国庆修改

This commit is contained in:
2025-10-05 19:44:37 +08:00
parent c8f40f3616
commit 34439f5cab
229 changed files with 17145 additions and 1724 deletions

159
README.en.md Normal file
View File

@@ -0,0 +1,159 @@
# Markdown To Web
> Turn a local Markdown notes folder into a deployable static React site.
This project scans all `.md` files inside `public/mengyanote/`, generates JSON datasets (directory tree, file contents, stats) at build time, and renders them clientside with React + `react-markdown`. The output is a **pure static site** (no server / no DB) suitable for GitHub Pages, Netlify, Vercel, or any static hosting.
Chinese documentation: see `README.md`.
## ✨ Features
- 🚀 Onecommand data generation (recursive scan → JSON assets)
- 📦 Fully static deployment (no runtime backend needed)
- 🧭 Expandable directory tree with stable IDs / paths
- 📝 Rich Markdown: GFM (tables, task lists, strikethrough), soft line breaks, math (KaTeX), code highlighting
- 🧮 Math via `remark-math` + `rehype-katex`
- 💡 Enhanced code blocks: language label + copy button
- 📚 Breadcrumb navigation derived from file path
- 🔌 Pluggable remark / rehype pipeline (easy to remove / replace)
- 🕶 Dark theme (fully customizable via CSS)
- 🔍 Multi-path fallback when fetching generated JSON (`/data``/src/data``./data`)
- 🧯 Ignore mechanism via `ignore.json`
- 📊 Basic statistics (file count, folder count, timestamp)
## 🛠 Tech Stack
- React 19 + Hooks + Context
- Vite
- `react-markdown` + remark / rehype plugins:
- `remark-gfm`, `remark-math`, `remark-breaks`
- `rehype-raw`, `rehype-katex`, `rehype-highlight`
- Highlight.js theme (replaceable)
- KaTeX (optional)
## ⚡ Quick Start
```bash
npm install
npm run dev # generate data + start dev server
npm run build # generate data + build static site (dist/)
npm run preview # preview production build
npm run generate-data # only regenerate JSON
npm run lint
```
## 📂 Key Structure
```
public/
mengyanote/ # Source Markdown tree (add your .md here)
data/ # Public copy of generated JSON
scripts/
generateData.js # Node script: scans + emits JSON
src/
data/ # Generated JSON (consumed in dev/build)
components/ # UI (MarkdownRenderer, Sidebar, ...)
context/ # AppContext (state + actions)
utils/fileUtils.js # Fetch + helpers + breadcrumbs
```
## 🧬 How It Works
1. `generateData.js` walks `public/mengyanote/` (applying `ignore.json` rules)
2. Builds:
- `directoryTree.json` (hierarchical tree)
- `fileContents.json` (path → raw markdown text)
- `stats.json` (counts + timestamp)
3. Writes duplicates to `src/data/` (for imports / dev) and `public/data/` (for runtime fetch in final build)
4. Frontend loads JSON (tries `/data``/src/data``./data`) and renders selected file
```
Markdown(.md) --> generateData.js --> JSON (tree, contents, stats) --> React (render)
```
## 🧯 Ignore Rules
Default ignored: `.obsidian`, `.trash`, `.git`, `node_modules`, any dotprefixed file/dir.
Custom ignore: create `public/mengyanote/ignore.json`:
```json
{ "ignore": ["Drafts", "private", "Scratch.md"] }
```
## 🎨 Customization Cheatsheet
| Goal | Where | Action |
|------|-------|--------|
| Remove math | `MarkdownRenderer.jsx` | Drop `remark-math` + `rehype-katex` + CSS import |
| Remove code highlighting | `MarkdownRenderer.jsx` | Remove `rehype-highlight` and theme CSS |
| New highlight theme | Add dependency | Replace imported highlight style |
| Inline code as plain text | Already done | Custom plugin strips inline code styling |
| Change image layout | `MarkdownRenderer.css` | Adjust `.markdown-image img` |
| Change breadcrumb format | `fileUtils.generateBreadcrumbs` | Modify join logic |
## 🚢 Deployment
Build output: `dist/` (fully static).
Platforms:
| Platform | Notes |
|----------|-------|
| GitHub Pages | Push `dist/` to `gh-pages` or use an Action |
| Netlify | Build: `npm run build`, Publish: `dist` |
| Vercel | Auto-detect Vite |
| Any static host / Nginx | Upload `dist/` |
Example Nginx:
```
server {
listen 80;
server_name example.com;
root /var/www/markdown-to-web/dist;
location / { try_files $uri /index.html; }
add_header Cache-Control "public, max-age=31536000";
}
```
## FAQ
**New file not appearing?** Regenerate data: `npm run generate-data` (or rerun dev/build).
**Ignore not working?** Ensure `ignore.json` sits directly in `public/mengyanote/` and key is exactly `ignore`.
**Large number of files causes slow load?** Consider splitting `fileContents.json` into perfile JSON (enhancement idea).
**Search support?** Not yet. You can integrate `minisearch` or `lunr` during build.
**Can I support frontmatter?** Extend the generator to parse YAML frontmatter and attach metadata to each node.
## Roadmap (Open for Contributions)
- [ ] Fulltext search
- [ ] Perfile lazy loading
- [ ] Recent updates panel / changelog
- [ ] Frontmatter tags + filters
- [ ] RSS / JSON feed generation
- [ ] PDF / print friendly mode
- [ ] Service Worker caching
- [ ] GitHub Actions deploy workflow example
## Contributing & License
Issues / PRs welcome. Please open an issue before large feature work; attach screenshots for UI changes. License: see `LICENSE`.
## Contact
Open an issue describing:
- Desired visual style (links / screenshots)
- Needed deployment pipeline (e.g., GitHub Pages Action)
- Theming or performance concerns
I can suggest patches or configuration examples.
---
Happy publishing! If you build cool themes (minimal / booklike / imagefirst), consider sharing them back. 🙌

183
README.md
View File

@@ -1,23 +1,47 @@
# Markdown To Web
此仓库将一组本地 Markdown 笔记(位于 `public/mengyanote`)转换成一个静态 React 网站,便于浏览、检索和发布到网络上
> 本地 Markdown 笔记目录“一键”编译为可部署的静态 React 网站。
本 README 为中文说明,包含项目简介、运行/构建步骤、目录结构、如何编辑笔记以及部署和贡献指南,方便直接发布到 GitHub
此仓库会读取 `public/mengyanote/` 下的所有 Markdown 文件,生成 JSON 数据,并在前端通过 React + react-markdown 渲染,最终可直接构建成 **纯静态站点**(可部署到 GitHub Pages / Netlify / Vercel / 任意 Nginx / OSS 等)
## 主要特性
本 README中文包含项目定位、特性说明、运行与构建、内部工作原理、目录结构、忽略与定制、部署方式、常见问题FAQ与未来计划Roadmap
- 从本地目录(`public/mengyanote`)递归读取 Markdown 文件并生成静态数据(`src/data`
- 使用 `react-markdown` 渲染 Markdown支持数学公式remark/rehype 插件)与代码高亮。
- 目录树侧边栏、内容渲染器等基础浏览功能。
- 通过简单脚本将 Markdown 文件转为项目需要的 JSON 数据,便于在静态站点中直接使用。
如需英文版本,请查看 `README.en.md`
## 技术栈
## ✨ 主要特性
- React 19
- Vite
- react-markdown + remark/rehype 插件remark-gfm、remark-math、rehype-katex、rehype-highlight 等)
- 🚀 **一键生成数据**:脚本自动递归扫描 `public/mengyanote`,提取目录树、文件内容与统计信息。
- 📦 **纯静态输出**:构建阶段就准备好全部数据,无需后端 / Serverless API。
- 🧭 **动态目录树**:可展开/折叠,每个节点保留唯一 `path``id`
- 📝 **Markdown 全面支持**GFM表格 / 任务列表 / 删除线、换行、数学公式KaTeX、代码块高亮Highlight.js
- 🧮 **数学公式渲染**`remark-math` + `rehype-katex`
- 💡 **代码体验增强**:复制按钮、语言标签、自动高亮。
- 📚 **面包屑导航**:基于文件路径实时生成。
- 🧱 **可定制渲染组件**:可移除/替换任意 remark/rehype 插件,或覆写组件(如链接、图片、表格、代码块)。
- 🕶 **深色风格**可自定义CSS 易修改,可换成“极简/原味/图片优先”布局。
- 🔍 **预生成数据多路径兜底**:前端运行时尝试从 `/data``/src/data` 加载,兼容不同构建/部署结构。
- 🧯 **忽略机制**:支持 `ignore.json` 自定义排除目录 / 文件。
- 📊 **统计信息**:生成文件总数、文件夹总数、生成时间等元数据。
## 快速开始Windows / cmd.exe
> 该项目适合:知识库发布、学习笔记归档、团队内部静态文档、离线备份网页化。
## 🛠 技术栈
- React 19 + Hooks + Context
- Vite 构建
- react-markdown + remark / rehype 插件:
- `remark-gfm`(扩展语法)
- `remark-math`(数学公式)
- `remark-breaks`(软换行)
- `rehype-raw`(允许原始 HTML 渲染)
- `rehype-katex`(数学公式渲染)
- `rehype-highlight`(代码高亮)
- Highlight.js 默认主题(可替换)
- KaTeX 样式(按需可移除)
所有数据生成逻辑在 **Node 脚本层** 完成,前端只做纯渲染,部署安全轻量。
## ⚡ 快速开始Windows / cmd.exe
1. 安装依赖
@@ -57,7 +81,7 @@ npm run generate-data
npm run lint
```
## 项目结构(重要文件/目录)
## 🧬 项目结构(重要文件/目录)
- `public/mengyanote/` - 源 Markdown 笔记目录(请把你的 .md 文件放在这里以纳入站点)。
- `scripts/generateData.js` - 将 Markdown 文件读取并生成 `src/data` 的脚本。
@@ -70,15 +94,26 @@ npm run lint
示例:如果你想让 Markdown 渲染更简洁(“换成我图片那种”样式),可以编辑 `src/components/MarkdownRenderer.css` 或直接修改 `MarkdownRenderer.jsx` 中的渲染类名/元素结构。
## 如何编辑/添加笔记
## ✍️ 如何编辑/添加笔记
1.`public/mengyanote` 下增加或修改 `.md` 文件,保持目录组织即可。
2. 运行 `npm run generate-data`(或 `npm run dev`)以重新生成 `src/data`
3. 刷新浏览器查看最新内容。
注意:脚本会忽略以 `.` 开头的文件/目录(例如 `.obsidian`)和一些预设项(`node_modules``.git` 等)。
### 忽略机制
## 部署建议
- 默认忽略:`.obsidian``.trash``.git``node_modules`、所有以 `.` 开头的文件或目录。
- 自定义忽略:在 `public/mengyanote/ignore.json` 中加入:
```jsonc
{
"ignore": ["临时", "草稿.md", "private"]
}
```
> ignore.json 位于源根目录(`public/mengyanote`),脚本运行时会打印“已加载忽略配置”。
## 🚢 部署建议
- 静态站点:`npm run build` 会在 `dist/` 生成静态文件,适合部署到 GitHub Pages、Netlify、Vercel、或任意静态文件托管服务。
- GitHub Pages构建后将 `dist/` 的内容发布到 gh-pages 分支或通过 GitHub Actions 自动发布。
@@ -93,20 +128,115 @@ npm run build
2.`dist/` 内容上传到你的静态托管服务或 gh-pages 分支。
如果需要,我可以为你添加一个自动部署到 GitHub Pages 的 GitHub Actions 工作流
如果需要,我可以为你添加一个自动部署到 GitHub Pages 的 GitHub Actions 工作流`deploy.yml`)。示例工作流会:
## 定制渲染样式
1. 触发条件:`push``main` 或手动触发
2. 安装依赖 & 运行 `npm run build`
3. 发布 `dist/``gh-pages` 分支
(如需请开 Issue 或在 PR 中请求)
### 其他托管方式
| 平台 | 方式 | 备注 |
|------|------|------|
| GitHub Pages | gh-pages 分支 / Action | 需启用 Pages |
| Netlify | 直接连接仓库 | Build 命令:`npm run build`Publish`dist` |
| Vercel | 导入项目 | 自动识别 Vite |
| OSS / Nginx | 上传 dist | 纯静态即可 |
### 基础 Nginx 配置示例
```nginx
server {
listen 80;
server_name example.com;
root /var/www/markdown-to-web/dist;
location / { try_files $uri /index.html; }
add_header Cache-Control "public, max-age=31536000";
}
```
## 🎨 定制渲染样式
如果你觉得当前 Markdown 美化太重并想改回更简洁的“图片优先”或原始样式:
- 编辑 `src/components/MarkdownRenderer.css`:移除或覆盖过度的样式(字体、背景、代码块高亮等)。
- 编辑 `src/components/MarkdownRenderer.jsx`:调整渲染器的 className、元素包装或忽略某些 remark/rehype 插件。
常见修改点
- 移除 KaTeX 或代码高亮:在 `src/components/MarkdownRenderer.jsx` 中删除相应的 rehype 插件 import 与使用。
- 简化图片样式:在 CSS 中将 img 的 max-width、margin 等属性调整为你想要的样式。
### 常见修改点
## 贡献与许可
| 需求 | 修改位置 | 说明 |
|------|----------|------|
| 去掉公式支持 | `MarkdownRenderer.jsx` | 移除 `remark-math` / `rehype-katex` 并删 CSS 引入 |
| 去掉代码高亮 | `MarkdownRenderer.jsx` | 移除 `rehype-highlight` 与对应样式 |
| 禁用内联代码特殊样式 | 已内置 | 自定义插件 `remarkDisableInlineCode` 已将 inlineCode 转普通文本 |
| 改图片最大宽度 | `MarkdownRenderer.css` | 调整 `.markdown-image img` |
| 更换高亮主题 | 安装新主题 | 引入其他 highlight.js CSS |
| 调整面包屑 | `fileUtils.generateBreadcrumbs` | 可改分隔符或格式 |
### 架构 / 数据流程简述
```
┌──────────────┐ 扫描/过滤 ┌────────────────┐ JSON写入 ┌───────────────┐
│ public/mengya │ ─────────────▶ │ generateData.js │ ───────────▶ │ src/data/*.json │
│ note/ (原始MD)│ ignore.json │ (Node脚本) │ public/data │ (目录/内容/统计)│
└──────────────┘ └────────────────┘ └───────────────┘
前端 (React 渲染)
```
前端加载策略:尝试按顺序 fetch`/data/...``/src/data/...``./data/...`
### 渲染组件要点
- 代码块组件:支持复制、语言标签、降尾换行处理
- 图片:包裹 `<figure>` + `<figcaption>`
- 标题:自动生成 `id` + 锚点链接
- 链接:区分内部/外部,`[[wikilink]]` 保留为文本标签(可扩展)
- 表格:外层包裹 div 实现横向滚动容器
### 性能提示
- Markdown 数量巨大时,可按需拆分 lazy-load目前为一次性加载全部 JSON
- 可拓展为:构建阶段拆分单文件 JSON运行时按需请求
- 可增加 Service Worker 缓存静态 JSON
## ❓ 常见问题FAQ
**Q: 为什么我新增文件后页面没有显示?**
A: 需要重新运行 `npm run generate-data``npm run dev` 让脚本再生成数据。
**Q: 想忽略某个目录但它还是出现?**
A: 确认 `ignore.json` 位于 `public/mengyanote/` 根目录,键名是 `ignore`,数组项不要写路径前缀。
**Q: 能否按需加载而不是一次性加载所有内容?**
A: 当前实现为构建期大 JSON。可拓展为“每文件单独 JSON + 动态 fetch”。
**Q: 是否支持搜索?**
A: 目前未实现。可通过构建阶段生成反向索引lunr / minisearch再前端搜索。
**Q: 图片如何放大预览?**
A: 可在 `MarkdownRenderer.jsx` 中为 `img` 包裹 Lightbox 组件或添加点击事件。
**Q: 可以改成多语言站点?**
A: 可在数据生成脚本中区分语言目录(如 `en/``zh/`),渲染层添加语言切换上下文。
## 🧭 Roadmap计划
- [ ] 全文搜索(可选:关键词高亮)
- [ ] 按需加载:文件内容拆分
- [ ] 文件更新 diff / 最近更新列表
- [ ] Tag/Frontmatter 支持YAML 解析)
- [ ] RSS / JSON Feed 输出
- [ ] 导出 PDF / 打印优化
- [ ] Service Worker 缓存加速
- [ ] GitHub Actions 自动部署工作流示例
欢迎在 Issue 中补充你的需求。
## 🤝 贡献与许可
欢迎提交 Issue 或 Pull Request。仓库包含 `LICENSE`(请查看该文件以确认许可证类型)。
@@ -114,14 +244,5 @@ npm run build
- 新功能请先创建 issue 讨论。
- 提交 PR 时附带简短说明和相关截图(如果是 UI 变更)。
## 联系方式
如需帮助或定制:在 Issue 中描述你想要的样式和示例图片/链接,我可以帮你修改 `MarkdownRenderer.css` / `MarkdownRenderer.jsx` 实现视觉风格。
---
祝发布顺利!如果你想,我可以:
- 帮你把 README 翻译成英文版本并放在 `README.en.md`
- 或直接替你修改 Markdown 渲染样式(把渲染改成更像你“图片那种”的风格)。

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>markdown-to-web</title>
<title>萌芽笔记</title>
</head>
<body>
<div id="root"></div>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6
public/data/stats.json Normal file
View File

@@ -0,0 +1,6 @@
{
"totalFiles": 163,
"totalFolders": 34,
"generatedAt": "2025-10-02T01:36:14.347Z",
"sourceDirectory": "E:\\React\\markdown-to-web\\public\\mengyanote"
}

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -1,6 +1,6 @@
{
"cssTheme": "Border",
"theme": "obsidian",
"theme": "moonstone",
"monospaceFontFamily": "American Typewriter",
"baseFontSize": 16
}

View File

@@ -13,12 +13,26 @@
"state": {
"type": "markdown",
"state": {
"file": "编程语言/Android/Linux配置安卓Gradle构建环境.md",
"file": "Docker/优秀好用的Docker镜像/NapCat-QQ机器人框架.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "Linux配置安卓Gradle构建环境"
"title": "NapCat-QQ机器人框架"
}
},
{
"id": "108d0380d5e0d36b",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "编程语言/C/C语言不设置临时变量交换x和y的值方法.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "C语言不设置临时变量交换x和y的值方法"
}
},
{
@@ -35,7 +49,8 @@
"title": "Docker命令集合"
}
}
]
],
"currentTab": 1
}
],
"direction": "vertical"
@@ -184,45 +199,53 @@
"obsidian-livesync:Show Customization sync": false
}
},
"active": "8304b0e105b08ed0",
"active": "108d0380d5e0d36b",
"lastOpenFiles": [
"编程语言/Android/安卓Gradle构建常用命令总结.md",
"树萌芽的小本本/目前已部署网站.md",
"树萌芽的小本本/网站小技巧.md",
"Docker/优秀好用的Docker镜像/FileCodeBox-文件快递柜.md",
"Docker/优秀好用的Docker镜像/Postgres数据库.md",
"Docker/优秀好用的Docker镜像/未命名.md",
"临时解决方案/2025年大一下暑假规划大方向.md",
"树萌芽的小本本/树萌芽の编程想法.md",
"树萌芽的小本本/检测IP的网站.md",
"树萌芽的小本本/重要信息记录.md",
"Docker/优秀好用的Docker镜像/Gitea-私有化仓库部署.md",
"Docker/Docker命令集合.md",
"Docker/优秀好用的Docker镜像/MySQL数据库.md",
"Docker/优秀好用的Docker镜像/MongoDB数据库.md",
"Docker/优秀好用的Docker镜像/Redis数据库.md",
"Docker/Docker 镜像相关.md",
"Docker/优秀好用的Docker镜像",
"Docker",
"大萌芽-Debian13服务器.md",
"Docker/Docker镜像快速迁移.md",
"无线-HCIA 02.md",
"Linux相关/把Ubuntu镜像源切换到阿里云.md",
"临时解决方案/修改hosts方式来直连Github.md",
"临时解决方案/萌芽云剪切板.md",
"2025年9月紧急规划.md",
"树萌芽制作的小东西/网站/万象口袋/软件名字.md",
"树萌芽制作的小东西/网站/树萌芽API集合/功能详情.md",
"树萌芽制作的小东西/网站/树萌芽API集合/API目录.md",
"树萌芽制作的小东西/网站/树萌芽API集合",
"树萌芽制作的小东西/网站/万象口袋",
"编程语言/Golang",
"编程语言/CSharp",
"编程语言/C++",
"编程语言/C语言",
"编程语言",
"计算机网络",
"图片文件夹/Screenshot_20250717_225309.jpg",
"Screenshot_20250717_225309.jpg"
"面试八股/Nacos功能与应用场景详解.md",
"临时/快速开启一个http服务器.md",
"Linux/wlan0简单介绍.md",
"杂项/计算机刷题网站.md",
"杂项/markdown格式没什么用.md",
"杂项/文本颜色测试.md",
"杂项/古诗.md",
"杂项/PTA好题-计算众数.md",
"杂项/PTA好题-英文单词排序.md",
"杂项/PTA万能代码.md",
"杂项/Markdown格式大全.md",
"杂项/c++如何快速的检测一个字串符在另一个字串符的出现次数.md",
"杂项/10月23日大学英语笔记.md",
"杂项",
"华为ICT/杂项/计算机刷题网站.md",
"华为ICT/杂项/文本颜色测试.md",
"华为ICT/杂项/古诗.md",
"华为ICT/杂项/PTA好题-计算众数.md",
"华为ICT/杂项/PTA好题-英文单词排序.md",
"华为ICT/杂项/PTA万能代码.md",
"华为ICT/杂项/markdown格式没什么用.md",
"华为ICT/杂项/Markdown格式大全.md",
"华为ICT/杂项/c++如何快速的检测一个字串符在另一个字串符的出现次数.md",
"华为ICT/杂项/10月23日大学英语笔记.md",
"华为ICT/杂项",
"临时/灵创第15周周报.md",
"临时/灵创第14周个人汇报.md",
"临时/第16周灵创汇报.md",
"临时",
"游戏引擎/Unity/Unity代码片段",
"游戏引擎/Unreal",
"游戏引擎/新建文件夹",
"游戏引擎/Unity",
"Minecraft/基岩版服务器/NukkitLearn/images/5-04.png",
"Minecraft/基岩版服务器/NukkitLearn/images/5-03.png",
"Minecraft/基岩版服务器/NukkitLearn/images/5-02.png",
"Minecraft/基岩版服务器/NukkitLearn/images/5-01.png",
"Minecraft/基岩版服务器/NukkitLearn/images/2-01.png",
"Minecraft/基岩版服务器/NukkitLearn/images/1-10.png",
"Minecraft/基岩版服务器/NukkitLearn/images/1-09.png",
"Minecraft/基岩版服务器/NukkitLearn/images/1-08.png",
"Minecraft/基岩版服务器/NukkitLearn/images/1-07.png",
"Minecraft/基岩版服务器/NukkitLearn/images/1-06.png",
"Minecraft/基岩版服务器/NukkitLearn/_config.yml",
"Minecraft/基岩版服务器/NukkitLearn/images",
"Minecraft/基岩版服务器/NukkitLearn"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -0,0 +1,6 @@
```php
use pocketmine\utils\TextFormat;
$this->getServer()->getLogger()->info(TextFormat::GREEN . "[刷矿机] 插件激活成功!");
```

View File

@@ -0,0 +1,179 @@
# 在阅读本教程前务必阅读本Readme
目前由官方承认的几个宣传贴
https://www.minebbs.com/threads/dreamserver-nukkit-nukkit.2912/#post-13183
https://www.mcplugin.cn/thread-1259-1-1.html
https://www.mcbbs.net/forum.php?mod=viewthread&tid=927009&page=1#pid15854025
https://www.mcbbs.net/forum.php?mod=viewthread&tid=930861&mobile=2
# 史上最详细的Nukkit教程
#### Learning Nukkit Whoever you are!
- [中文(简体)](README.md)
- [English](README_en.md)
#### 说明
您可以在这里作为一本书观看教程: http://noyark.net/
本教程遵循[发现者小组公约](LICENSE)
#### 友情项目
[基于Java开发的SCPSL服务端](https://github.com/jsmod2-java-c/JSmod2-Core)
#### 关于主要作者
事实上作者上已经退坑了,但是不影响写教程,只是有几点还请悉知:
- 不要问如何搭建服务器;
- 不要PM任何有关人员定制插件
- 除了写这个教程作者不会参与任何和Minecraft有关的活动和交易
- 本教程需要一定的Java基础请先做好预习功课并且作者不会回答任何基础的问题
- 作者目前是高中学生,因此时间并不充裕,更新速度请大家海涵;
- 如有任何疏漏或Bug的存在还请指正
#### 作者的扯皮
Nukkit核心作为一个服务端开发框架虽然性能优越吸引了大批的开发者加入开发但教程稀少学习难度大使很多小白
望而却步,基于目前很多人对于教程的渴望,包括作者在初学的时候只能通过到处询问和看核心源码来了解如何使用一个东西,
但并不是所有的人都能安心去看核心源码或者到处询问问题。同时,本教程会重复强调一件事情: 打好基础,本教程已经列出
您需要掌握的java基础什么0基础直接学习nukkit都是骗人的作者已经见过很多这样的初学者很少的人通过这种方式
学会(无疑是一种懒散的行为)nukkit事实上就是学习一个新的api,并无很大的入门难度,只要您学习了基础,就能很快从
这里得到启发并参与到nukkit的大家庭。
主作者其实很和蔼,但是对于简单的问题作者由于时间问题是不能回答的,也不能干刷屏之类的事情,再和蔼的人也会把你给
屏蔽当然作者坚持开放原则如果有相关问题可以issues发表您的疑问。
同时记得给项目一个star支持一下并且将这个项目宣传给别人在支持作者同时造福他人。您的支持和宣传就是作者写下去的动力
希望这个教程可以帮到您,我们由衷的感谢:)
--- MagicLu550
#### 关于本教程
Nukkit官方说过: Nukkit是一款高性能的核能驱动的Minecraft基岩版服务器,它的
速度更快性能相比PocketMine更高。
```
Nukkit is nuclear-powered server software for Minecraft Bedrock Edition. It has a few key advantages over other server software:
Written in Java, Nukkit is faster and more stable.
Having a friendly structure, it's easy to contribute to Nukkit's development and rewrite plugins from other platforms into Nukkit plugins.
Nukkit is under improvement yet, we welcome contributions.
```
目前依托Java语言的健壮性,Nukkit形成了强大的生态并且出现了很多分支目前使用最广泛的分支是[NukkitX](http://nukkitx.com)
感谢您能观看我们编写的教程。该教程在编写阶段,更新间隔可能会很长。
请谅解。该教程是为了简化目前很多晦涩难懂的教程使得有过Java语法基础的朋友
能方便地了解Nukkit和学习Nukkit. 目前Nukkit的学习难度主要在于其资料过少。
我们将会整理和参考其他相关资料来编写该教程,也感谢您参与编写,造福大家
同时转发本教程应当附上GitHub原地址不得任意转发和搬运以及商业使用。
本教程不涉及语法只涉及Nukkit的各种库的解释以便于开发
您所需要掌握的最基本的Java基础体系
```
--基础部分
-- 语法
-- 变量定义
-- 方法定义
-- 基本表达式
-- 逻辑表达式
-- 算数表达式
-- 流程控制
-- 条件语句
-- 循环语句
-- 选择语句
-- 面向对象
-- 类,对象的概念
-- 定义类和声明对象
-- 包的概念
-- 匿名内部类
-- 接口
-- 抽象类,抽象方法
-- lambda表达式
-- 面向对象的三大特征
-- 继承
-- 封装
-- 多态
-- 枚举
-- 注解
-- 类库
-- 反射
-- 多线程
-- 字符串操作
-- 数字包装类
-- io流
-- bio
-- nio
-- 套接字Socket
-- udp
-- tcp
-- 时间类库
-- 系统类库
```
未来我们会开发Nukkit-d,是对于Nukkit设计思想的整理和重新实现以解决目前nukkit维护难的问题
#### 关于我们
如果您有兴趣可以随时发送Pr等
本教程不定期更新,大概寒假时期更新最快
相关意见可以联系843983728@qq.com或者加QQ:843983728
QQ群: 931210534
![QQ群](images/0-00.png)
#### 贡献标准和须知
1. 教程要求尽量自然,易懂,符合本项目所追求的"人人可以学习Nukkit"的宗旨
2. 编写教程可以在参与编写者添加自己的名字,若没有可以自己手动加入,如第一章所同
3. 照搬其他教程原文需要得到作者同意,并且在下面注明参考文献。
4. 若发现侵权行为,与本项目领导者无关,但我们会积极配合找到侵权者
5. 请尽量以第一章为范本编写您的内容。
6. 如果有知识缺点请在Readme里注明这个缺点出处写在知识缺点里,知识缺点在章节中标注。
7. 教程补充首要的是解决知识缺点
8. 由于编写人数较少,教程不避免的会有错误漏洞不严谨如果发现可以发送pullRequest参与修改
#### 知识缺点:
- 关于fallBackPrefix的作用 - 第二章和第四章 [1]
#### 专栏
- [填坑专栏](专栏-关于我们常见的那些坑.md)
- [用指令设置玩家实体大小](章外篇之—-用指令设置玩家实体大小(简单版).md)
- [多语言解决方案](章外篇之二-多语言解决方案.md)
#### 目录
- [第一部分 基础准备](第一部分前言.md)
- [X] [如何搭建开发环境](第一章-如何搭建环境.md)
- [X] [插件要素](第二章-插件要素.md)
- [X] [如何编写监听器](第三章-如何编写监听器.md)
- [X] [如何编写指令](第四章-如何编写命令.md)
- [X] [如何使用配置文件](第五章-如何使用配置文件.md)
- [X] [如何编写plugin.yml](第六章-如何编写plugin.yml.md)
- [X] [PluginBase类](第七章-PluginBase类.md)
- [X] [练习案例](第八章-案例玩家进入信息等效果.md)
- [第二部分 nukkit的工具和各种事件介绍](第二部分前言.md)
- [X] [主要的事件的介绍](第一章-主要的事件介绍.md)
- [ ] [事件相关方法](第二章-事件相关方法.md)
- [X] [计时器的介绍](第三章-计时器的介绍.md)
- [X] [Server类和PluginManager类](第四章-Server类和PluginManager类.md)
- [ ] [各种实体类的方法介绍](第五章-各种实体类的方法介绍.md)
- [ ] [各种工具类的介绍](第六章-各种工具类的介绍.md)
- [X] [如何发送数据包](第七章-如何发送数据包.md)
- 第三部分 nukkit相关实例
- 第四部分 nukkit原理剖析
#### 专栏状况
```diff
+ 专题
+ 提示信息
```

View File

@@ -0,0 +1 @@
theme: jekyll-theme-minimal

View File

@@ -0,0 +1,12 @@
# 填坑专栏
#### 这里整理一些容易出现的坑,这里会有解决方案.
##### 大家可以把自己平时的坑写进去,写上解决方案
##### 或者等待别人写上解决方案
1. ClassCastException
这种是类转换问题的锅,可以查看下
2. EventException: null
3. Main类名称指错的问题
4. ClassNotFoundException的问题
5. NullPointerException的常见问题
6. 忘记注册监听器的坑

View File

@@ -0,0 +1,57 @@
## Nukkitlearn章外篇之---用指令设置玩家实体大小(简单版)
本章外篇教程由zsy制作本教程非常的简单目的是为萌新们打开一个很好的思路
如果你对本篇有任何合理地建议或者疑问可以联系笔者QQ:910223023笔者高三 可能回复不会及时,但看到后一定处理。
1. 首先你需要按照章内的教程创建一个项目名称随意我这里用的SetSize。配置好一系列的东西并创建好plugin.yml后可以开始了。
2. 这是我的plugin.yml中的内容
```
name: SetSize
main: xyz.zsy.SetSize.SetSize
version: "1.0.0"
api: ["1.0.0"]
load: POSTWORLD
author: zsy
website: xyz.zsy
```
这里再次强调一个坑main中的路径应是主类的所在的位置***精确到文件名***我这里xyz.zsy.SetSize.SetSize的意思是xyz\zsy\SetSize目录下的SetSize.java,读者应该根据自己的包和主类名字修改否则Nukkit不会加载到插件的主类插件是无法运行的因为Nukkit无从下手。
3.之后在主类中添加内容,添加的内容如下
```java
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equals("ss")) { //判断斜杠后的指令名字 /ss用法/ss 玩家名字 大小
if (sender instanceof Player) { //判断发令者是否是玩家
if (args.length>0) { //判断指令/ss 后是否有参数
Player p = this.getServer().getPlayer(args[0]); //获取/ss 后的第一个参数应该是String类
if (args.length>1) { //同上 判断是否有第二个参数 也就是是否有 大小 这个参数,应该是个数字
Float size = Float.parseFloat(args[1]); //获取大小并将它转化为Float型因为Nukkit中设置实体大小的方法参数要求是Float
p.setScale(size); //调用setScale()方法来设置玩家大小
return true;
}
}
}
}
return false;
}
```
4.不要忘了在plugin.yml加入权限组不然这个指令将会是非法的。
```yaml
commands:
ss:
description: Set A Player's Size.
usage: "/ss [name] [size]"
permission: SetSize.ss
permissions:
SetSize.ss:
default: op
```
5.检查没有丢步骤后,就可以编译并且测试了! 当然设置的玩家大小当玩家重新进入服务器或者死亡重生后将会变为正常大小因为本篇设置的只是一个临时变量实体消失后它也会一起被清理如果想要永久的保存则可以利用config储存下来大小然后在进服和复活事件中进行设置感兴趣的可以进一步咨询笔者那么本教程就到此结束了祝你在胜利之地越战越勇

View File

@@ -0,0 +1,477 @@
# NukkitLearn章外篇之二-插件中的多语言解决方案
参与编写者: [innc11](https://github.com/innc11)
##### 知识点: 配置文件、HashMap、枚举、文本替换、可变参数、正则表达式
本章外篇教程innc11制作目的尽可能地简化插件开发过程中对多语言的处理。不得不说对多语言的支持是插件的一大加分项特别是将插件发布到Nukkit的国际论坛上更是如此如果有任何的疑问欢迎随时提issue。
## 0.HashMap
所谓HashMap就是保存着一个个键值对的类一个存储数据的类形象的描述HashMap就相当于中药铺的药材柜子柜子上有着数不清的小屉子每个小屉子里都放着不同的药材比如胖大海、金银花、荷叶等等每个小屉子上都有一个标签写着这个屉子里放着什么药材这样就形成了一个关系一个标签对应一个药材这就是键值对的关系一对一的关系也是HashMap的存储结构但也有一些限制比如不能出现两个一模一样的标签不然HashMap就无法判断到底需要哪一个小屉子的药材但可以允许两个屉子里的东西一模一样屉子可以是空的但标签绝对不能为空。
HashMap能够处理一些数组无法处理的数据比如一个统计玩家击杀数的插件当某个玩家干掉10个怪物以后就给其发放奖励这时候就需要对每个玩家进行记录把玩家名作为标签击杀数作为屉子里的东西每次只需要根据玩家名把对应屉子打开把里面的数据拿出来+1然后放回去关上屉子即可。这时候便出现了一个关系每一个玩家名对应一个击杀数。
```
zhangsan = 5
lisi = 3
wangwu = 8
```
玩家名就是键击杀数就是值大致就是这个结构可以随时根据玩家名获取到对应击杀数这是HashMap的工作方式。
## 1. 原理
插件首先从配置文件加载所有语言到一个HashMap里在需要时从这个HashMap里读出来再进行相应变量替换后显示给玩家。
## 2. 创建项目
1. 首先创建一个项目在这里我使用一个当玩家破坏方块时给玩家发送一个消息告诉玩家破坏的方块的ID这里插件名字就叫 Tips配置好依赖后首先是```plugin.yml```
```yaml
name: Tips
main: exam.miner.TipsPlugin
version: "1.0"
api: ["1.0.0"]
```
## 3. 使用语言类
3. 首先我们声明一个语言类这个类非常简单仅仅包含一个HashMap、构造方法、获取语言的方法
```lang```负责存储所有的语言文本,```String getLang()```负责从```lang```里面获取对应的文本并做参数替换,在构造方法里我们往```lang```里面添加2个语言其中```BROKE_MESSAGE```和```PLACE_MESSAGE```是关键字,我们通过传递给```getLang()```一个关键字来获取对应的文本,```getLang()```会在```lang```里面用关键字去进行查找,并返回对应的文本
```java
public class MyLang
{
HashMap<String, String> lang = new HashMap<String, String>();
public MyLang()
{
lang.put("BROKE_MESSAGE", "你破坏了一个方块");
lang.put("PLACE_MESSAGE", "你放置了一个方块");
}
public String getLang(String key)
{
return lang.get(key);
}
}
```
接下来是主类:
在主类中使用刚才的```MyLang```类,并注册监听器,当玩家在破坏或者放置一个方块时,去获取对应的文本,然后发送给玩家
```java
public class TipsPlugin extends PluginBase implements Listener
{
MyLang lang;
@Override
public void onEnable()
{
lang = new MyLang();
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerBrokeBlock(BlockBreakEvent e)
{
String message = lang.getLang("BROKE_MESSAGE"); // 你放置了一个方块
e.getPlayer().sendMessage(message);
}
@EventHandler
public void onPlayerPlaceBlock(BlockPlaceEvent e)
{
String message = lang.getLang("PLACE_MESSAGE"); // 你破坏了一个方块
e.getPlayer().sendMessage(message);
}
}
```
这就是最简单的方式但实际开发中语言往往是从配置文件进行加载的而不是写死在代码里接下来就是如何从yml文件进行读取加载
## 3. 从yaml配置文件加载语言
1. 我们使用language.yml文件用来保存语言文本
```yaml
# language.yml
PLACE_MESSAGE: "你放置了一个方块"
BROKE_MESSAGE: "你破坏了一个方块"
```
1. 接着我们需要修改我们的语言文件,使其从配置文件进行加载,首先需要在```MyLang```类里额外添加一个```Config config```变量,和一个```void reload()```方法,我们手动调用```reload()```方法来从配置文件加载语言文本。
2. 构造方法我们需要添加一个参数,用来告诉```MyLang```类应该读取哪一个yml文件不建议在构造方法中立即调用```reload()```,因为当对象构造的时候```language.yml```可能根本就不存在。非常建议在插件主类中保存默认配置文件后手动调用```reload()```
3. 在新添加的```void reload()```方法中,首先是命令```config```(重新)加载一下,然后把```lang```中已经存在的数据全部删除掉,接着就是使用```getKeys(false)```来获取```config```中所有的key就是上面yml中的```PLACE_MESSAGE```和```BROKE_MESSAGE```,这个方法会以```Set<String>```的形式返回我们使用foreach进行遍历即可需要说明的是参数中的```false```指```boolean child```我们只需要根节点上的key不需要子节点上的key传```false```即可
4. 在foreach中我们定义一个变量value来放置获取到的"key对应的值"也就是```你放置了一个方块```和```你破坏了一个方块```接下来我们需要进行一个判断,如果这个值是```String```类型的,我们就把它添加到```lang```里面,如果不是,比如```int```,```bool```,或者```list```类型,则跳过。
```java
public class MyLang
{
Config config;
HashMap<String, String> lang = new HashMap<String, String>();
public MyLang(String languageFileName)
{
config = new Config(new File(getDataFolder(), languageFileName), Config.YAML);
}
public void reload()
{
config.reload();
lang.clear();
for(String key : config.getKeys(false))
{
Object value = config.get(key.name());
if(value instanceof String)
{
lang.put(key, (String) value);
}
}
}
public String getLang(String key)
{
return lang.get(key);
}
}
```
5. 主类需要在```new MyLang()```时传递文件名。当然也要把```language.yml```以前打包进插件里。在onEnable()里要调用```saveResource("language.yml", false)```把```language.yml```写入到插件**DataFolder**里
```java
public class TipsPlugin extends PluginBase implements Listener
{
MyLang lang;
@Override
public void onEnable()
{
saveResource("language.yml", false);
lang = new MyLang("language.yml");
lang.reload();
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerBrokeBlock(BlockBreakEvent e)
{
String message = lang.getLang("BROKE_MESSAGE"); // 你放置了一个方块
e.getPlayer().sendMessage(message);
}
@EventHandler
public void onPlayerPlaceBlock(BlockPlaceEvent e)
{
String message = lang.getLang("PLACE_MESSAGE"); // 你破坏了一个方块
e.getPlayer().sendMessage(message);
}
}
```
7. 在实际使用中,我们只需要修改```language.yml```中文字,然后使用指令调用```MyLang.reload()```重新加载即可,但在复杂的插件中,只有这些功能时远远不够的,语言文件不能一成不变,有时候需要将文字中的一部分字符替换成各种实际数据,比如商店插件在交易完成时会显示这笔交易花费了多少多少钱,玩家死亡时会显示被谁谁谁干掉了,其中的"钱"和"击杀者"就是实际的数据,需要根据实际情景来决定具体应该是什么。这就涉及到参数化的问题,将文本中一部分文字使用实际数据进行替换。
## 4. 参数化
1. 参数化必然会涉及到**占位符**这个概念,拿一个例子来说
```yaml
PLACE_MESSAGE: "你放置了ID为 ${BLOCK_ID} 的方块"
```
其中的**${BLOCK_ID}**就是占位符,他只是给实际的数据占个位置而已,并不会被显示出来。当然风格可以自己定义,在这个例子中,我们使用```${占位符名字}```这种风格。
2. 我们修改我们的```MyLang```类的```getLang()```方法,使其可以动态替换占位符,具体的调用方式为```MyLang.getLang("PLACE_MESSAGE", "{BLOCK_ID}", String.valueOf(block.getId()));```
3. 多个参数的调用方式
```
MyLang.getLang("PLACE_MESSAGE",
"{BLOCK_ID}", String.valueOf(block.getId(),
"{PLAYER_NAME}", player.getName(),
));
```
4. 无参数的调用方式
```
MyLang.getLang("PLACE_MESSAGE"));
```
5. 后面的占位符和实际数据总是成双成对的出现,这可以大幅加快开发效率,当然这需要```MyLang.getLang()```具有对应的支持,具体看下面的示例代码。
```java
public class MyLang
{
Config config;
HashMap<String, String> lang = new HashMap<String, String>();
public MyLang(String languageFileName)
{
config = new Config(new File(getDataFolder(), languageFileName), Config.YAML);
}
public void reload()
{
config.reload();
lang.clear();
for(String key : config.getKeys(false))
{
Object value = config.get(key.name());
if(value instanceof String)
{
lang.put(key, (String) value);
}
}
}
public String getLang(String key, String... argsPair) // 这里使用可变参数,当做数组一样处理即可
{
String rawStr = lang.get(key);
int argCount = argsPair.length / 2; // 计算出有多少"对"参数,末尾的孤立参数会被舍弃
for(int i=0;i<argCount;i++)
{
String reg = argsPair[i*2]; // 占位符
String replacement = argsPair[i*2+1]; // 具体数值
// 风格检查,检查是否以{开头,以}结尾
if(reg.startsWith("{") && reg.endsWith("}"))
{
reg = reg.replaceAll("\\{", "\\\\{"); // 构建正则表达式,把{替换成\{
reg = reg.replaceAll("\\}", "\\\\}"); // 构建正则表达式,把}替换成\}
rawStr = rawStr.replaceAll("\\$"+reg, replacement); // 执行替换
// 最终reg 等于 \$\{占位符名字\} 并在rawStr中执行替换
}
}
return rawStr;
}
}
```
6. 这样我们就可以随意组合占位符和参数了
7. 我们修改```language.yml```
```yaml
# language.yml
PLACE_MESSAGE: "你放置了一个ID ${ID} 的方块"
BROKE_MESSAGE: "${PLAYER} 破坏了一个ID ${ID} 的方块"
```
8. 再修改主类以添加支持
```java
public class TipsPlugin extends PluginBase implements Listener
{
MyLang lang;
@Override
public void onEnable()
{
saveResource("language.yml", false);
lang = new MyLang("language.yml");
lang.reload();
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerBrokeBlock(BlockBreakEvent e)
{
String playerName = e.getPlayer().getName(); // 这里把int 转换为 String 类型
String id = String.valueOf(e.getBlock().getId());
String message = lang.getLang("BROKE_MESSAGE", "{PLAYER}", playerName, "{ID}", id);
e.getPlayer().sendMessage(message);// xxx破坏了一个ID 137 的方块
}
@EventHandler
public void onPlayerPlaceBlock(BlockPlaceEvent e)
{
String id = String.valueOf(e.getBlock().getId());
String message = lang.getLang("PLACE_MESSAGE", "{ID}", id);
// 你放置了一个ID 137 的方块
e.getPlayer().sendMessage(message);
}
}
```
9. 到这里还没有结束,在一个大型插件项目中往往有着数量极多语言文本,经常上百行是很常见的问题,如果每次都手动去输入关键字,难免会出现差错,而且效率也会大打折扣,这个时候我们就需要引入一个新的内容,**枚举**通过把关键字定义成枚举可以由IDE快速补齐也会大大减少因拼写错误的BUG。
## 5. 枚举常量
1. 我们需要在MyLang中额外定义一个enum
```java
public enum L // L : Language
{
PLACE_MESSAGE,
BROKE_MESSAGE,
public String getDefaultLangText()
{
return name();
}
}
```
2. 在L中有两个成员常量和```getDefaultLangText()```方法,字如其意,用于获取默认的文本,这里直接返回字段名。也就是"```PLACE_MESSAGE```"、"```BROKE_MESSAGE```"
3. 由于enum的引入```reload()```也要发生变化,```lang```的泛型也发生了变化,```getLang()```的参数也发生了变化,看代码!
```java
public class MyLang
{
Config config;
// 开始使用 HashMap<L, String> 将MyLang.L作为键(key)以提高效率
HashMap<L, String> lang = new HashMap<L, String>();
public MyLang(String languageFileName)
{
config = new Config(new File(getDataFolder(), languageFileName), Config.YAML);
}
public void reload()
{
config.reload();
lang.clear();
// 一个标志如果有缺少的关键字会被补全然后保存config以便调试
boolean supplement = false;
// 现在是以Lang.values()进行遍历而不是config.getKeys(),注意
for(L key : L.values())
{
Object value = config.get(key.name());
// 如果这个关键字不存在,会自动补齐,并设置标志位
if(v==null)
{
config.set(key.name(), key.getDefaultLangText());
supplement = true;
lang.put(key, key.getDefaultLangText());
}
if(value instanceof String)
{
lang.put(key, (String) value);
}
}
// 如果有补齐则需要保存这个config以便用户可以在config内查看到以定位问题
if(supplement)
{
config.save();
}
}
public String getLang(L key, String... argsPair) // 这里使用可变参数,当做数组一样处理即可
{
String rawStr = lang.get(key);
int argCount = argsPair.length / 2; // 计算出有多少"对"参数,末尾的孤立参数会被舍弃
for(int i=0;i<argCount;i++)
{
String reg = argsPair[i*2]; // 占位符
String replacement = argsPair[i*2+1]; // 具体数值
// 风格检查,检查是否以{开头,以}结尾
if(reg.startsWith("{") && reg.endsWith("}"))
{
reg = reg.replaceAll("\\{", "\\\\{"); // 构建正则表达式,把{替换成\{
reg = reg.replaceAll("\\}", "\\\\}"); // 构建正则表达式,把}替换成\}
rawStr = rawStr.replaceAll("\\$"+reg, replacement); // 执行替换
// 最终reg 等于 \$\{占位符名字\} 并在rawStr中执行替换
}
}
return rawStr;
}
public enum L // L : Language
{
PLACE_MESSAGE,
BROKE_MESSAGE,
// 当config中找不到时的默认文本这里直接返回字段名
public String getDefaultLangText()
{
return name();
}
}
}
```
4. 主类开始使用enum作为关键字
```java
// 可以使用static import 来简化代码
//static import MyLang.L.*;
public class TipsPlugin extends PluginBase implements Listener
{
MyLang lang;
@Override
public void onEnable()
{
saveResource("language.yml", false);
lang = new MyLang("language.yml");
lang.reload();
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPlayerBrokeBlock(BlockBreakEvent e)
{
String playerName = e.getPlayer().getName(); // 这里把int 转换为 String 类型
String id = String.valueOf(e.getBlock().getId());
// 这里使用常量作为关键字
String message = lang.getLang(MyLang.L.BROKE_MESSAGE, "{PLAYER}", playerName, "{ID}", id);
e.getPlayer().sendMessage(message);// xxx破坏了一个ID 137 的方块
}
@EventHandler
public void onPlayerPlaceBlock(BlockPlaceEvent e)
{
String id = String.valueOf(e.getBlock().getId());
// 这里使用常量作为关键字
String message = lang.getLang(MyLang.L.PLACE_MESSAGE, "{ID}", id);
// 你放置了一个ID 137 的方块
e.getPlayer().sendMessage(message);
}
}
```
5. enum的优点特别明显能避免拼写错误和支持IDE提示当然enum也有缺点必须要保持yml的关键字和enum成员字段名一致才行。
## 6. 总结
本篇只是一个最简单的解决方案,当然大家还可以按自己的需求添加更多的功能,如果感兴趣,可以参考我的另一个大量使用了此多语言方案的**[插件项目](https://github.com/innc11/QuickShopX/blob/master/src/cn/innc11/QuickShopX/config/LangConfig.java)**添加了对config中多行文字写法的支持和彩色代码的支持。如果有任何问题欢迎提交issue感谢阅读

View File

@@ -0,0 +1,162 @@
[上一章](第八章-案例玩家进入信息等效果.md) [下一章](第二章-事件相关方法.md)
# 第二部分 第一章 主要的事件介绍
参与编写者: SmallasWater MagicLu550
#### 建议学习时间: 40分钟
##### 学习要点: 了解基本的事件
转载于[[教程] [原创][Wiki][NPS] NukkitX 事件大全](https://www.mcbbs.net/thread-813733-1-1.html)
- block: 方块类
- BlockBreakEvent: 当一个方块被玩家破坏的时候调用本事件
- BlockBurnEvent: 当一个方块被火烧掉的时候触发此事件
- BlockFadeEvent: 当一个方块因自然原因消失或衰落时触发此事件
- BlockFormEvent: 当一个方块因为自然变化被放置、更改或者蔓延时(如下雪)触发此事件
- BlockFromToEvent: 液体流动/龙蛋自己传送的事件(源方块到目标方块)
- BlockGrowEvent: 当一个方块在世界中自然生长的时触发此事件(如小麦生长)
- BlockIgniteEvent: 当一个方块被点燃时触发
- BlockPistonChangeEvent: 活塞臂状态变化事件
- BlockPlaceEvent: 当一个方块被玩家放置的时候触发此事件
- BlockRedstoneEvent: 当方块接受到的红石信号变化时触发此事件
- BlockSpreadEvent: 当一个方块基于自然法则地蔓延时触发此事件(如菌丝的蔓延)
- BlockUpdateEvent: 方块更新事件(如你操作静态水周围方块时会触发本事件,导致水流淌)
- DoorToggleEvent: 开关门事件
- ItemFrameDropItemEvent: 物品展示框丢出物品事件
- LeavesDecayEvent: 当树叶消失时触发此事件
- LiquidFlowEvent: 当液体流动时触发此事件
- SignChangeEvent: 在玩家设置牌子上的内容子时触发
- entity: 实体类
- CreatureSpawnEvent: 使用生成蛋时触发该事件
- CreeperPowerEvent: 当爬行者被闪电击中时触发该事件
- EntityArmorChangeEvent: 实体护甲变化事件
- EntityBlockChangeEvent: 实体改变方块事件
- EntityCombustByBlockEvent: 当方块造成实体燃烧时触发该事件
- EntityCombustByEntityEvent: 当一个实体造成另外一个实体燃烧时触发该事件
- EntityCombustEvent: 当实体燃烧时触发该事件
- EntityDamageByBlockEvent: 当一个实体受到来自方块的伤害时触发该事件
- EntityDamageByChildEntityEvent: 当一个实体受到另一个子实体的伤害时触发该事件
- EntityDamageByEntityEvent: 当一个实体受到另外一个实体伤害时触发该事件
- EntityDamageEvent: 储存伤害事件的数据
- EntityDeathEvent: 当任何一个实体死亡时触发本事件
- EntityDespawnEvent: 实体被移除事件
- EntityExplodeEvent: 当一个实体爆炸的时候触发本事件
- EntityExplosionPrimeEvent: 当一个实体决定爆炸时调用(苦力怕的闪烁)
- EntityInteractEvent: 当一个实体与其他物体互交时触发本事件
- EntityInventoryChangeEvent: 实体存储变化事件
- EntityLevelChangeEvent: 当一个实体进入另一个世界时触发该事件
- EntityMotionEvent: 实体运动事件
- EntityPortalEnterEvent: 当一个实体与传送门接触时触发本事件
- EntityRegainHealthEvent: 实体生命回复事件
- EntityShootBowEvent: 生命实体射出箭时触发
- EntitySpawnEvent: 实体生成事件
- EntityTeleportEvent: 当非玩家实体(如末影人)试图从一个位置传送到另一个位置时触发
- EntityVehicleEnterEvent: 实体进入载具事件
- EntityVehicleExitEvent: 实体退出载具事件
- ExplosionPrimeEvent: 当一个实体决定爆炸时调用(TNT的闪烁)
- ItemDespawnEvent: 从世界中移除掉落物时会调用此事件(因为掉落满5分钟)
- ItemSpawnEvent: 世界中产生掉落物时会调用此事件
- ProjectileHitEvent: 当一个抛射物击中物体时触发本事件
- ProjectileLaunchEvent: 当一个抛射物被发射时触发本事件
- inventory: 物品格类
- BrewEvent: 当酿造完成时触发这个事件
- CraftItemEvent: 当一个物品被合成的时候触发这个事件
- FurnaceBurnEvent: 当一个物品作为燃料被燃烧的时候触发这个事件
- FurnaceSmeltEvent: 当一个物品被熔炼完毕时触发这个事件
- InventoryClickEvent: 当玩家点击物品栏中的格子时触发事件事件
- InventoryCloseEvent: 当玩家关闭背包时触发本事件
- InventoryMoveItemEvent: 非玩家触发格子物品传输时触发此事件(如漏斗)
- InventoryOpenEvent: 当玩家打开背包时触发本事件
- InventoryPickupArrowEvent: 当漏斗/漏斗矿车收起发射出去的箭时触发本事件
- InventoryPickupItemEvent: 当漏斗/漏斗矿车收起掉落的物品时触发本事件
- InventoryTransactionEvent: 村民交易事件
- StartBrewEvent: 开始酿造事件
- level: 世界类
- ChunkLoadEvent: 当一个区块被加载时调用
- ChunkPopulateEvent: 当一个新的区块填充完毕时调用
- ChunkUnloadEvent: 当一个区块被卸载时调用
- LevelInitEvent: 当一个世界被初始化时调用
- LevelLoadEvent: 当一个世界被加载时调用
- LevelSaveEvent: 当一个世界被保存时调用
- LevelUnloadEvent: 当一个世界被卸载时调用
SpawnChangeEvent: 一个在世界的出生点被改变时调用的事件
ThunderChangeEvent: 同天气类下同名事件
WeatherChangeEvent: 同天气类下同名事件
- player: 玩家类
- PlayerAchievementAwardedEvent: 当玩家获得某个成就时触发此事件
- PlayerAnimationEvent: 玩家动作事件
- PlayerAsyncPreLoginEvent: 存储尝试登录的玩家的详细信息,玩家尝试登录服务器的事件
- PlayerBedEnterEvent: 玩家躺在床上时触发此事件
- PlayerBedLeaveEvent: 玩家离开床时触发此事件
- PlayerBlockPickEvent: 玩家捡拾方块事件
- PlayerBucketEmptyEvent: 玩家用完一只桶后触发此事件
- PlayerBucketFillEvent: 玩家装满一只桶后触发此事件
- PlayerChatEvent: 玩家聊天/使用命令会触发本事件
- PlayerChunkRequestEvent: 玩家请求区块事件
- PlayerCommandPreprocessEvent: 玩家执行命令时触发(/开头)
- PlayerCreationEvent: 创建玩家事件
- PlayerDeathEvent: 玩家死亡事件
- PlayerDropItemEvent: 玩家丢出物品事件
- PlayerEatFoodEvent: 玩家吃食物事件
- PlayerFoodLevelChangeEvent: 玩家饥饿度变化事件
- PlayerFormRespondedEvent: 玩家操作GUI(按钮/滑块/输入框/选择框)事件
- PlayerGameModeChangeEvent: 当玩家游戏模式发生变化时调用此事件
- PlayerInteractEntityEvent: 当玩家点击一个实体时调用此事件
- PlayerInteractEvent: 当玩家对一个对象或空气进行交互时触发本事件
- PlayerInvalidMoveEvent: 当玩家不正确地移动时触发
- PlayerItemConsumeEvent: 当玩家使用了一个消耗品(食物/药水/牛奶桶)时触发
- PlayerItemHeldEvent: 玩家手持某物品事件
- PlayerJoinEvent: 玩家进入服务器事件
- PlayerKickEvent: 玩家被服务器踢出事件
- PlayerLoginEvent: 玩家尝试登录的事件
- PlayerMapInfoRequestEvent: 玩家请求地图信息事件
- PlayerMouseOverEntityEvent: 玩家鼠标停留在实体上事件
- PlayerMoveEvent: 玩家移动事件
- PlayerPreLoginEvent: 玩家尝试登录服务器事件
- PlayerQuitEvent: 玩家离开服务器事件
- PlayerRespawnEvent: 玩家重生事件
- PlayerServerSettingsRequestEvent: 玩家请求服务器设置事件
- PlayerSettingsRespondedEvent: 响应玩家设置事件
- PlayerTeleportEvent: 玩家传送事件
- PlayerToggleFlightEvent: 玩家切换飞行状态时触发
- PlayerToggleGlideEvent: 玩家切换滑翔状态时触发
- PlayerToggleSneakEvent: 玩家切换潜行状态时触发
- PlayerToggleSprintEvent: 玩家切换疾跑状态时触发
- PlayerToggleSwimEvent: 玩家切换游泳状态时触发
- plugin: 插件类
- PluginDisableEvent: 插件禁用事件
- PluginEnableEvent: 插件启用事件
- potion: 药水类
- PotionApplyEvent: 药水生效事件
- PotionCollideEvent: 药水冲突事件
- redstone: 红石类
- RedstoneUpdateEvent: 红石更新事件
- server: 服务端类
- BatchPacketsEvent: 批处理数据包事件
- DataPacketReceiveEvent: 数据包接收事件
- DataPacketSendEvent: 数据包发送事件
- PlayerDataSerializeEvent: 玩家数据序列化事件
- QueryRegenerateEvent: GameSpy4协议监听事件
- RemoteServerCommandEvent: 这个事件当服务器RCON收到指令时调用
- ServerCommandEvent: 这个事件当服务器后台发送指令时调用. 这是命令开始处理过程之前被触发的
- vehicle: 载具类
- EntityEnterVehicleEvent: 实体进入载具事件
- EntityExitVehicleEvent: 实体退出载具事件
- VehicleCreateEvent: 载具创建事件
- VehicleDamageEvent: 载具被伤害的事件
- VehicleDestroyEvent: 载具被损毁的事件(包含被玩家/自然损坏)
- VehicleMoveEvent: 载具移动事件
- VehicleUpdateEvent: 载具更新事件
- weather: 天气类
- LightningStrikeEvent: 雷击事件
- ThunderChangeEvent: 世界打雷事件
- WeatherChangeEvent: 天气改变事件
其中的方法介绍
本内容将会在[第二章](第二章-事件相关方法.md)提到,也可以参考[bukkit文档](https://bukkit.windit.net/javadoc/)
参考文献
- [[教程] [原创][Wiki][NPS] NukkitX 事件大全](https://www.mcbbs.net/thread-813733-1-1.html)
- [bukkit中文doc](https://bukkit.windit.net/javadoc/)
[上一章](第八章-案例玩家进入信息等效果.md) [下一章](第二章-事件相关方法.md)

View File

@@ -0,0 +1,314 @@
[下一章](第二章-插件要素.md)
# 第一部分 第一章 如何搭建环境
参与编写者: MagicLu550
#### 建议学习时间: 40分钟
##### 学习要点: 学会使用idea搭建maven环境和nukkit开发环境,并学习调试nukkit项目
一. 环境搭建
搭建环境是我们使用nukkit的第一步虽然其中下载IDE不是必要的一步但是是我们进行简便开发的第一步。
1. 首先上[idea官网](https://www.jetbrains.com/idea/download/#section=mac)下载我们所需要的版本,我们
一般选择免费的Community版本当然您也可以选择购买Ultimate版本,Community版本其实已经符合我们使用的需求当然
作者使用的是Ultimate版本在此教学。
![1-01](images/1-01.png)
2.打开idea后我们这里使用[maven](https://maven.apache.org/)项目进行构建,这里使用[jdk1.8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html),为这些选项即可
![1-02](images/1-02.png)
3.输入完groupId和artifactId(这些是自定义的),创建项目后,我们会进入这个界面,这里我artifactId为MyFirstPlugin
![1-03](images/1-03.png)
```
转自Snake1999: 这里有必要做一些详细说明。GroupId 经常也是 Java 项目的包名。Java 项目的包命名规则有这样的约定:
以 网站倒着写.项目名字 或者 网站倒着写.项目名字.模块名字 为结构
必须以小写字母开头
必须与别的 Java 包相区别
Nukkit 内核的所有包都是遵循这个规定的 Nukkit 的网站是 nukkit.cn所以里面包含所有方块block的包的包名应该是
cn.nukkit.block
各位在编写 Nukkit 插件的时候,需要编写一个独特的、和他人的项目不一样的包名,以便与他人开发的插件相区别。比如译者的网站是 snake1999.com如果译者编写了一个叫 ExamplePlugin 的 Nukkit 插件,就可以放在包名为 com.snake1999.exampleplugin 的包内。以下包名的结构都是可以使用的:
com.snake1999.exampleplugin
net.mcbbs.tutorialplugin
ru.nukkit.nkexample
me.fromgate.firstplugin
而以下的包名都是不被推荐使用的:
main.java.plugin
TestPlugin
另外,开发 Nukkit 插件,我们规定不能把插件的任何部分存放在 cn.nukkit 包下,否则后果自负。
关于主类的名称只要能和别人的主类区分开就可以了但是不推荐使用MainClass之类的名称。
```
4. 在pom.xml中加入以下标签可以导入
```xml
<distributionManagement>
<repository>
<id>releases</id>
<name>nukkitx-releases</name>
<url>https://repo.nukkitx.com/release</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>nukkitx-snapshots</name>
<url>https://repo.nukkitx.com/snapshot</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>nukkitx-repo</id>
<name>nukkitx-snapshot</name>
<url>https://repo.nukkitx.com/snapshot</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>cn.nukkit</groupId>
<artifactId>nukkit</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
```
如图所示
![1-04](images/1-04.png)
5. 接下来在程序上可以进行开发了
二. 项目结构
1.nukkit的基本项目结构为
```
-- project
-- src
-- main-class
-- resources
-- plugin.yml
```
因此我们需要创建主类和plugin.yml(插件配置文件)
转自snake1999:
- 每个插件都有 plugin.yml 文件。
- 这里介绍一些设置,说明这个文件的格式。
- 插件没有这个文件,就不会被 Nukkit 识别和加载。
- 这个文件的最基本的应该是类似这样的:
```yaml
name: FirstPlugin # nukkit运行时识别的插件名
main: net.noyark.www.Example # 主类名称
version: "0.0.1" # 版本号
author: 你的名字,这里指作者名称
api: ["1.0.8"] # 早期nukkit api为1.0.0
# 目前大概为1.0.8
description: My first plugin #介绍
```
目前大概为这种结构
![1-05](images/1-05.png)
但是我们项目目前这样是不够的Example类需要继承PluginBase才可以成为真正可以加载的主类
```java
package net.noyark.www;
import cn.nukkit.plugin.PluginBase;
public class Example extends PluginBase {
/**
* 在服务器加载完成前执行一次
*/
@Override
public void onLoad() {
}
/**
* 在服务器加载完成后执行一次
*/
@Override
public void onEnable() {
}
/**
* 服务器即将关闭前执行一次
*/
@Override
public void onDisable() {
}
}
```
三. 调试插件
其实调试插件很简单,这里我们需要这几个过程
- 编译,打包
- 部署
- 运行加载
- 调试
1. 编译打包很简单,我们使用ideaIDE可以轻松的实现maven打包
PS:当然在打包之前建议在pom.xml里加下这个可以解决idea的编译器版本
无效等一系列问题
```xml
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
```
当然项目是jdk8演示的如果您不是jdk8则需要修改一个地方
jdk9
```xml
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.9</source>
<target>1.9</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
```
jdk10
```xml
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.10</source>
<target>1.10</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
```
以此类推....
如图所示添加这些标签
![1-08](images/1-08.png)
如图中按钮所示(不要在意其中的项目)
![1-06](images/1-06.png)
之后我们找到其中的package,点击即可
![1-07](images/1-07.png)
如图所示,出现"BUILD SUCCESS"我们就成功了
![1-09](images/1-09.png)
一般的目标jar包会产生在target文件夹中
![1-10](images/1-10.png)
2. 部署
之后把打包好的合法插件丢入plugins文件里就完美运行了这里将不做图示
这就是插件部署的过程。
3. 运行加载
如果出现了加载$(您的插件名字)成功之类的字样,说明您的插件成功了。
4. 之后进入游戏测试您的插件是否符合您的需求,根据您插件出现的各种问题
进行再次调试,修改,打包等
[下一章](第二章-插件要素.md)
参考文献
- [[教程] [官方] 使用Maven配置Nukkit开发环境的方法部分](https://www.mcbbs.net/thread-706178-1-1.html)
- [Nukkit插件从0开始](https://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html)
- [[教程] [转载] 如何开始制作 Nukkit 插件? Fromgate 的插件教程来了! [长文|多图] ](https://www.mcbbs.net/forum.php?mod=viewthread&tid=552265&page=1&authorid=100001)

View File

@@ -0,0 +1,17 @@
# 第一部分 基础准备
本部分将讲解nukkit插件的基础部分学习完这一部分你就可以独自实现一个
自己的插件了,感谢您开始观看这个教程。
本部分分为八个章节
- [如何搭建开发环境](第一章-如何搭建环境.md)
- [插件要素](第二章-插件要素.md)
- [如何编写监听器](第三章-如何编写监听器.md)
- [如何编写指令](第四章-如何编写命令.md)
- [如何使用配置文件](第五章-如何使用配置文件.md)
- [如何编写plugin.yml](第六章-如何编写plugin.yml.md)
- [PluginBase类](第七章-PluginBase类.md)
- [练习案例](第八章-案例玩家进入信息等效果.md)
每个章节都具有关联性,必须看完第一个才可以进行下一个,这样才能条理的学习

View File

@@ -0,0 +1,376 @@
[上一章](第六章-如何编写plugin.yml.md) [下一章](第八章-案例玩家进入信息等效果.md)
# 第一部分 第七章 PluginBase类
参与编写者: MagicLu550
#### 建议学习时间: 60分钟
##### 学习要点: 了解PluginBase的方法
这里将会详细的介绍PluginBase类的常用方法。
PluginBase实现了Plugin接口是插件运行的委托父类。它的子类被认定为服务端的运行类它有
三个运行方法,在[第一章](第一章-如何搭建环境.md)已经提到,它的很多方法都是开发过程最常用的,
也是一切api调用的入口和途径
PluginBase.java
```java
package cn.nukkit.plugin;
import cn.nukkit.Server;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.PluginIdentifiableCommand;
import cn.nukkit.utils.Config;
import cn.nukkit.utils.Utils;
import com.google.common.base.Preconditions;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
/**
* 一般的Nukkit插件需要继承的类。<br>
* A class to be extended by a normal Nukkit plugin.
*
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @see cn.nukkit.plugin.PluginDescription
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
abstract public class PluginBase implements Plugin {
private PluginLoader loader;
private Server server;
private boolean isEnabled = false;
private boolean initialized = false;
private PluginDescription description;
private File dataFolder;
private Config config;
private File configFile;
private File file;
private PluginLogger logger;
public void onLoad() {
}
public void onEnable() {
}
public void onDisable() {
}
public final boolean isEnabled() {
return isEnabled;
}
/**
* 加载这个插件。<br>
* Enables this plugin.
* <p>
* <p>如果你需要卸载这个插件,建议使用{@link #setEnabled(boolean)}<br>
* If you need to disable this plugin, it's recommended to use {@link #setEnabled(boolean)}</p>
*
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final void setEnabled() {
this.setEnabled(true);
}
/**
* 加载或卸载这个插件。<br>
* Enables or disables this plugin.
* <p>
* <p>插件管理器插件常常使用这个方法。<br>
* It's normally used by a plugin manager plugin to manage plugins.</p>
*
* @param value {@code true}为加载,{@code false}为卸载。<br>{@code true} for enable, {@code false} for disable.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final void setEnabled(boolean value) {
if (isEnabled != value) {
isEnabled = value;
if (isEnabled) {
onEnable();
} else {
onDisable();
}
}
}
public final boolean isDisabled() {
return !isEnabled;
}
public final File getDataFolder() {
return dataFolder;
}
public final PluginDescription getDescription() {
return description;
}
/**
* 初始化这个插件。<br>
* Initialize the plugin.
* <p>
* <p>这个方法会在加载(load)之前被插件加载器调用,初始化关于插件的一些事项,不能被重写。<br>
* Called by plugin loader before load, and initialize the plugin. Can't be overridden.</p>
*
* @param loader 加载这个插件的插件加载器的{@code PluginLoader}对象。<br>
* The plugin loader ,which loads this plugin, as a {@code PluginLoader} object.
* @param server 运行这个插件的服务器的{@code Server}对象。<br>
* The server running this plugin, as a {@code Server} object.
* @param description 描述这个插件的{@code PluginDescription}对象。<br>
* A {@code PluginDescription} object that describes this plugin.
* @param dataFolder 这个插件的数据的文件夹。<br>
* The data folder of this plugin.
* @param file 这个插件的文件{@code File}对象。对于jar格式的插件就是jar文件本身。<br>
* The {@code File} object of this plugin itself. For jar-packed plugins, it is the jar file itself.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final void init(PluginLoader loader, Server server, PluginDescription description, File dataFolder, File file) {
if (!initialized) {
initialized = true;
this.loader = loader;
this.server = server;
this.description = description;
this.dataFolder = dataFolder;
this.file = file;
this.configFile = new File(this.dataFolder, "config.yml");
this.logger = new PluginLogger(this);
}
}
public PluginLogger getLogger() {
return logger;
}
/**
* 返回这个插件是否已经初始化。<br>
* Returns if this plugin is initialized.
*
* @return 这个插件是否已初始化。<br>if this plugin is initialized.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final boolean isInitialized() {
return initialized;
}
/**
* TODO: FINISH JAVADOC
*/
public PluginIdentifiableCommand getCommand(String name) {
PluginIdentifiableCommand command = this.getServer().getPluginCommand(name);
if (command == null || !command.getPlugin().equals(this)) {
command = this.getServer().getPluginCommand(this.description.getName().toLowerCase() + ":" + name);
}
if (command != null && command.getPlugin().equals(this)) {
return command;
} else {
return null;
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
return false;
}
@Override
public InputStream getResource(String filename) {
return this.getClass().getClassLoader().getResourceAsStream(filename);
}
@Override
public boolean saveResource(String filename) {
return saveResource(filename, false);
}
@Override
public boolean saveResource(String filename, boolean replace) {
return saveResource(filename, filename, replace);
}
@Override
public boolean saveResource(String filename, String outputName, boolean replace) {
Preconditions.checkArgument(filename != null && outputName != null, "Filename can not be null!");
Preconditions.checkArgument(filename.trim().length() != 0 && outputName.trim().length() != 0, "Filename can not be empty!");
File out = new File(dataFolder, outputName);
if (!out.exists() || replace) {
try (InputStream resource = getResource(filename)) {
if (resource != null) {
File outFolder = out.getParentFile();
if (!outFolder.exists()) {
outFolder.mkdirs();
}
Utils.writeFile(out, resource);
return true;
}
} catch (IOException e) {
Server.getInstance().getLogger().logException(e);
}
}
return false;
}
@Override
public Config getConfig() {
if (this.config == null) {
this.reloadConfig();
}
return this.config;
}
@Override
public void saveConfig() {
if (!this.getConfig().save()) {
this.getLogger().critical("Could not save config to " + this.configFile.toString());
}
}
@Override
public void saveDefaultConfig() {
if (!this.configFile.exists()) {
this.saveResource("config.yml", false);
}
}
@Override
public void reloadConfig() {
this.config = new Config(this.configFile);
InputStream configStream = this.getResource("config.yml");
if (configStream != null) {
DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(dumperOptions);
try {
this.config.setDefault(yaml.loadAs(Utils.readFile(this.configFile), LinkedHashMap.class));
} catch (IOException e) {
Server.getInstance().getLogger().logException(e);
}
}
}
@Override
public Server getServer() {
return server;
}
@Override
public String getName() {
return this.description.getName();
}
/**
* 返回这个插件完整的名字。<br>
* Returns the full name of this plugin.
* <p>
* <p>一个插件完整的名字由{@code 名字+" v"+版本号}组成。比如:<br>
* A full name of a plugin is composed by {@code name+" v"+version}.for example:</p>
* <p>{@code HelloWorld v1.0.0}</p>
*
* @return 这个插件完整的名字。<br>The full name of this plugin.
* @see cn.nukkit.plugin.PluginDescription#getFullName
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final String getFullName() {
return this.description.getFullName();
}
/**
* 返回这个插件的文件{@code File}对象。对于jar格式的插件就是jar文件本身。<br>
* Returns the {@code File} object of this plugin itself. For jar-packed plugins, it is the jar file itself.
*
* @return 这个插件的文件 {@code File}对象。<br>The {@code File} object of this plugin itself.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
protected File getFile() {
return file;
}
@Override
public PluginLoader getPluginLoader() {
return this.loader;
}
}
```
当然我们从这里面可以抓住几个要点:
- 运行方法: onLoad(),onEnable(),onDisable()
- 获得方法: boolean isEnabled()
,boolean isDisabled(),
File getDataFolder(),
PluginDescription getDescription(),
PluginLogger getLogger(),
boolean isInitialized(),
PluginIdentifiableCommand getCommand(String name),
Config getConfig(),
Server getServer(),
String getName(),
String getFullName(),
File getFile(),
PluginLoader getPluginLoader()
- 设置的方法: setEnabled(boolean enabled)
- 初始化的方法: init(PluginLoader loader, Server server, PluginDescription description, File dataFolder, File file)
- 命令方法: boolean onCommand(CommandSender sender, Command command, String label, String[] args)
- 产生配置文件方法: InputStream getResource(String filename),
boolean saveResource(String filename),
boolean saveResource(String filename, boolean replace)
boolean saveResource(String filename, String outputName, boolean replace),
void saveConfig(),
void saveDefaultConfig(),
void reloadConfig()
这里介绍一下常用的方法
1. isEnabled和isDisabled分别是查看插件是否执行过onEnable方法和isDisable方法,插件状态为isEnabled时方可进行注册监听器等操作
2. setEnabled()是强行执行onEnabled,调用它会执行一次onEnabled,无参数方法默认将isEnabled改为true
3. getDataFolder() 获得插件的附属文件夹,文件路径为plugins/插件名称
4. getDescription() 得到插件plugin.yml的相关信息
5. getLogger() 得到日志对象,可以通过它进行日志输出的操作
6. isInitialized() 返回这个插件是否已经初始化
7. getCommand(String name) 获得插件的指定名称的指令对象 # 这个不太明确
8. getConfig() 获得默认的配置文件对象
9. getServer() 获得服务端对象
10.getName() 获得插件名称,在plugin.yml标记的
11.getFullName() 获得插件完整的名字,一个插件完整的名字由"名字+v+版本号"组成
12.getFile() 获得插件文件的对象
13.getPluginLoader() 获得插件加载器对象
14.init(...) 当插件开始被加载读取时,调用该方法初始化插件的基本内容
15.onCommand(...) 执行plugin.yml中定义的命令
16.getResource(...) 见[第五章](第五章-如何使用配置文件.md)
17.saveConfig() saveDefaultConfig()保存默认配置文件
18.reloadConfig()重新加载配置文件,使得里面信息程序可以读取到
[上一章](第六章-如何编写plugin.yml.md) [下一章](第八章-案例玩家进入信息等效果.md)

View File

@@ -0,0 +1,224 @@
[上一章](第六章-各种工具类的介绍.md) [下一章]()
# 第二部分 第七章 如何发送数据包
参与编写者: MagicLu550
#### 建议学习时间: 40分钟
##### 学习要点: 了解数据包和主要的发送形式
一. 概述
Nukkit实现客户端与服务端交互,是通过发送和接收数据包实现的.数据包在nukkit
的工作过程是占有很重的分量,包括玩家的移动等,都是由一个个数据包接连不断的实现这一
功能.实现收发数据包的机制是RakNet,通过UDP实现的这些功能.RakNet实现的基础是
Netty框架,如下文可以看到
cn/nukkit/raknet/server/UDPServerSocket.java
```
package cn.nukkit.raknet.server;
import cn.nukkit.utils.ThreadedLogger;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOption;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* author: MagicDroidX
* Nukkit Project
*/
public class UDPServerSocket extends ChannelInboundHandlerAdapter {
```
RakNetServer.java
```
@Override
public void run() {
this.setName("RakNet Thread #" + Thread.currentThread().getId());
Runtime.getRuntime().addShutdownHook(new ShutdownHandler());
UDPServerSocket socket = new UDPServerSocket(this.getLogger(), port, this.interfaz);
try {
new SessionManager(this, socket);
} catch (Exception e) {
Server.getInstance().getLogger().logException(e);
}
}
```
二. Netty框架
[Netty框架](https://github.com/netty/netty)是使用最广泛的java-nio框架之一,由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具
用以快速开发高性能、高可靠1性的网络服务器和客户端程序。Netty相当于简化和流线化了网络应用的编程开发过程.
如果你想要更深入研究这个框架可以参见[Netty框架的github](https://github.com/netty/netty)
三. Nukkit的发包机制
1. Nukkit 数据包的结构
Nukkit的数据包类的继承结构是
```
BinaryStream
|------- DataPacket
|-------- 我们主要操作的数据包
```
pid() 一般为数据包的NETWORK_ID在Player,Server,RakNetInterface,DataPacket类中被调用过
DataPacket的主要方法是decode()和encode(),数据包的传输过程中,通过这两个方法实现解码和
编码,使得数据包在服务端与客户端间相互识别.
decode() 是解码方法,一般是客户端发来的数据包,解码到对象的具体属性,之后在服务端中使用这些数据,
即 客户端 -> 服务端
如代码中这样
CameraPacket.java
```
package cn.nukkit.network.protocol;
import lombok.ToString;
@ToString
public class CameraPacket extends DataPacket {
public long cameraUniqueId;
public long playerUniqueId;
@Override
public byte pid() {
return ProtocolInfo.CAMERA_PACKET;
}
@Override
public void decode() {
this.cameraUniqueId = this.getVarLong();
this.playerUniqueId = this.getVarLong();
}
```
encode() 是编码方法,会在发包时被调用,将在服务端设置的数据值写入发出到客户端
即 服务端 -> 客户端
如代码这样
CameraPacket.java
```
@Override
public void encode() {
this.reset();
this.putEntityUniqueId(this.cameraUniqueId);
this.putEntityUniqueId(this.playerUniqueId);
}
```
从这里,我们就可以引入接下来的发包环节,事实上,它很简单
2. 事件
发送数据包和接收数据包的时候会触发几种事件,我们可以通过这几种事件进行
抓包
我们比较常用的是这几种
- BatchPacketsEvent: 批处理数据包事件
- DataPacketReceiveEvent: 数据包接收事件
- DataPacketSendEvent: 数据包发送事件
这里我们主要介绍Receive和Send
DataPacketSendEvent主要触发在服务端向客户端发送数据包的时候
Player.java
```
public int dataPacket(DataPacket packet, boolean needACK) {
if (!this.connected) {
return -1;
}
try (Timing timing = Timings.getSendDataPacketTiming(packet)) {
//There!!!!!
DataPacketSendEvent ev = new DataPacketSendEvent(this, packet);
this.server.getPluginManager().callEvent(ev);
if (ev.isCancelled()) {
return -1;
}
```
DataPacketReceiveEvent主要触发在客户端向服务端发送数据包并且服务端接收到的时候.
Player.java
```
public void handleDataPacket(DataPacket packet) {
if (!connected) {
return;
}
try (Timing timing = Timings.getReceiveDataPacketTiming(packet)) {
//There!!!!!!!!!
DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet);
this.server.getPluginManager().callEvent(ev);
if (ev.isCancelled()) {
return;
}
```
3. 发包
Nukkit提供了友好的数据包机制我们可以通过需求定义,发送数据包
Nukkit提供了发送数据包的方法,并允许开发者直接发送数据包和监听数据包的收发
一般的发送数据包的方式都是使用玩家对象的dataPacket实现
`player.dataPacket(DataPacket)`,这是一个最常用的方式。
当然,先前的Server类也提到了批量发包的方法(Server类)
* batchPackets(Player[], DataPacket[]) 批量发送数据包
* broadcastPacket(Player[], DataPacket) 向所有玩家广播数据包
这三个方法就是发包所常使用的方法了。
这里我们用dataPacket方法做案例
这里用MovePlayerPacket做一个样例
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
import cn.nukkit.network.protocol.MovePlayerPacket;
public class TestListener implements Listener {
@EventHandler
public void onPlayer(PlayerJoinEvent e){
//1. 定义数据包对象
MovePlayerPacket packet = new MovePlayerPacket();
//2. 设置数据包数值,这里我随便写了几个值
packet.x = 0;
packet.y = 100;
packet.z = 1000;
//3.发出
e.getPlayer().dataPacket(packet);
}
}
```
四. 常用数据包的解释
[上一章](第六章-各种工具类的介绍.md) [下一章]()

View File

@@ -0,0 +1,249 @@
[上一章](第二章-插件要素.md) [下一章](第四章-如何编写命令.md)
# 第一部分 第三章 如何编写监听器
参与编写者: MagicLu550,innc11
#### 建议学习时间: 30分钟
##### 学习要点: 学习如何构建一个简单的监听器,和自己构造事件
其实看一看这章,没有啥可讲的。监听器的内容很简单,很多人认识它的困难主要是概念上,
而不是使用上。
注册监听器有两个步骤: 1.定义监听器 2.注册监听器
nukkit监听器的构成: 事件监听和优先级
nukkit的监听器是通过 **反射(reflect)** 实现的,因此基于它,开发者容易上手,且上手
更简便。nukkit的监听器设计形同bukkit也易于bukkit上手
nukkit声明一个事件的监听管理器是通过注解实现的@EventHandler
@EventHandler的源码如图
```java
package cn.nukkit.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义一个事件的处理器的注解。<br>
* Annotation that defines a handler.
*
* <p>一个处理器的重要程度被称作处理器的<b>优先级</b>,优先级高的处理器有更多的决定权。参见:{@link #priority()}<br>
* The importance of a handler is called its <b>priority</b>, handlers with higher priority speaks louder then
* lower ones. See: {@link #priority()}</p>
*
* <p>处理器可以选择忽略或不忽略被取消的事件,这种特性可以在{@link #ignoreCancelled()}中定义。<br>
* A handler can choose to ignore a cancelled event or not, that can be defined in {@link #ignoreCancelled()}.</p>
*
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @see cn.nukkit.event.Listener
* @see cn.nukkit.event.Event
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
/**
* 定义这个处理器的优先级。<br>
* Define the priority of the handler.
*
* <p>Nukkit调用处理器时会按照优先级从低到高的顺序调用这样保证了高优先级的监听器能覆盖低优先级监听器做出的处理。
* 调用的先后顺序如下:<br> </p>
* When Nukkit calls all handlers, ones with lower priority is called earlier,
* that make handlers with higher priority can replace the decisions made by lower ones.
* The order that Nukkit call handlers is from the first to the last as:
* <ol>
* <li>EventPriority.LOWEST
* <li>EventPriority.LOW
* <li>EventPriority.NORMAL
* <li>EventPriority.HIGH
* <li>EventPriority.HIGHEST
* <li>EventPriority.MONITOR
* </ol>
*
* @return 这个处理器的优先级。<br>The priority of this handler.
* @see cn.nukkit.event.EventHandler
*/
EventPriority priority() default EventPriority.NORMAL;
/**
* 定义这个处理器是否忽略被取消的事件。<br>
* Define if the handler ignores a cancelled event.
*
* <p>如果为{@code true}而且事件发生,这个处理器不会被调用,反之相反。<br>
* If ignoreCancelled is {@code true} and the event is cancelled, the method is
* not called. Otherwise, the method is always called.</p>
*
* @return 这个处理器是否忽略被取消的事件。<br>Whether cancelled events should be ignored.
* @see cn.nukkit.event.EventHandler
*/
boolean ignoreCancelled() default false;
}
```
第一个是优先级,第二个是是否忽略事件被取消
默认的优先级是normal,从注释可以知道这是他们的先后顺序LOWEST会最先被调用其次是LOW最后是MONITOR如果在LOWEST监听器中调用了Event.setCancelled(true)Nukkit则会忽略掉后面的 ignoreCancelled 被设置为true或者保持默认的优先级更高的监听器
- EventPriority.LOWEST
- EventPriority.LOW
- EventPriority.NORMAL
- EventPriority.HIGH
- EventPriority.HIGHEST
- EventPriority.MONITOR
而实现监听器的优先级标记是通过 **枚举(Enum)** 实现的
优先级的目的是为了保证监听器按照顺序执行,以使得一个监听器操作完会
进入下一个监听器继续执行,以确保执行的有序性。下一个监听器的相同操作
会覆盖之前监听器的相同操作。
同时,事件可以被我手动取消的,但是有时候事件虽然取消,但依然需要操作,
那么ignoreCancelled可以发挥作用了,当然默认是忽略掉取消的事件,也就是
说取消的事件默认不会被监听(这里可能有所错误,我这个不太常用)
一个EventHandler基本都是监听一个事件的(我只尝试过一个事件的),代码中
这样使用
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e){
//执行代码
}
}
```
这里的意思就是当玩家进入服务器时就会触发PlayerJoinEvent事件服务器会
形成一个PlayerJoinEvent对象并且调用先前注册的有关PlayerJoinEvent的
EventHandler将对象传入这样就实现了一个 **事件的调用**
获取对象的内容则通过e调用即可我们不需要很明白它的具体细节只需要知道
使用EventHandler注解的方法(且有一个Event的子类类型参数并且它所在的监听器被注册)
就会在事件发生时,相应的被调用,如上文所讲,这里的代码案例就是当发生玩家加入时,
这个onPlayerJoin方法会被服务端调用。
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e){
e.getPlayer().sendMessage("你好 "+e.getPlayer());
}
}
```
例如这个代码,在玩家加入时,将会向玩家发送一个"你好 玩家的名字",这就是
我们监听器的作用
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.EventPriority;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler(priority = EventPriority.HIGH,ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent e){
e.getPlayer().sendMessage("你好 "+e.getPlayer());
}
}
```
如果要使用我们之前所说的参数,则这样使用。
事实上监听器的基本使用方式也就这些nukkit也提供了很多事件给予我们使用也允许我们
自己制作事件自己使用,在第二部分中,我们将会讲解提供了哪些事件
如何自己定义一个事件
事实上nukkit的事件是通过callEvent调用的所以我们同样可以通过callEvent实现我们自己
的事件。
```
this.getServer().getPluginManager().callEvent(Event e);
```
首先我们先定义一个事件类
```java
package net.noyark.www;
import cn.nukkit.event.Event;
public class MyEvent extends Event {
}
```
之后,我们在监听器使用我们的事件
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
public class OtherListener implements Listener {
@EventHandler
public void onMy(MyEvent e){
}
}
```
之后[注册监听器](第二章-插件要素.md),我们就可以使用我们的事件了。
```
this.getServer().getPluginManager().registerEvents(new OtherListener(),this);
```
如何触发我们的事件?
事件的触发则通过callEvent触发假如我们写一个玩家假如时如果他的
名字叫abc就触发MyEvent事件
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler
public void onMy(MyEvent e){
}
@EventHandler
public void onPLayerJoin(PlayerJoinEvent e){
if("abc".equals(e.getPlayer().getName())){
Example.getPlugin().getServer()
.getPluginManager()
.callEvent(new MyEvent());
}
}
}
```
这里,我们完成了我们的自定义事件
假如我们要注册一个EventHandler不去注册其他的该如何。
事实上可以实现这个nukkit提供了registerEvent方法
可以注册单个EventHandler但不太常用我这里也不会再过多
阐述了如果想了解可以发issue我将会添加这方面的内容
[上一章](第二章-插件要素.md) [下一章](第四章-如何编写命令.md)

View File

@@ -0,0 +1,343 @@
[上一章](第二章-事件相关方法.md) [下一章](第四章-Server类和PluginManager类.md)
# 第二部分 第三章 计时器的介绍
参与编写者:`zzz1999`
#### 建议学习时间3分钟
##### 学习要点:了解计时器的功能以及如何使用
一.概念
1.计时器的作用是 __立即延时循环或延时循环__ 某些任务,通过计时器我们可以实现例如 *在线奖励* , *倒计时* 等插件功能, 是一些复杂插件中经常用到的功能之一.
2.执行计时器代码块的单元叫做任务,我们可以编写任务代码来使用计时器的功能.
3.任务执行分为两种类型,一种是同步,一种是异步.同步通过主线程所调度管理执行,异步通过主线程调度管理,异步线程池执行.所以,太多的任务会导致服务器卡顿.
4.计时器的功能以`minecraft刻`(tick)作为单位,一刻为现实时间的0.05秒,现实时间的1秒为20刻,服务器每秒刻数由TPS决定,最高为20.
二.代码
我们先来看一下nukkit的[Task](https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/scheduler/Task.java)类.
```java
package cn.nukkit.scheduler;
import cn.nukkit.Server;
/**
* 表达一个任务的类。<br>A class that describes a task.
*
* <p>一个任务可以被Nukkit服务器立即延时循环或延时循环执行。参见:{@link ServerScheduler}<br>
* A task can be executed by Nukkit server with a/an express, delay, repeat or delay&amp;repeat.
* See:{@link ServerScheduler}</p>
*
* <p>对于插件开发者,为确保自己任务能够在安全的情况下执行(比如:在插件被禁用时不执行),
* 建议让任务继承{@link PluginTask}类而不是这个类。<br>
* For plugin developers: To make sure your task will only be executed in the case of safety
* (such as: prevent this task from running if its owner plugin is disabled),
* it's suggested to use {@link PluginTask} instead of extend this class.</p>
*
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract class Task implements Runnable {
private TaskHandler taskHandler = null;
public final TaskHandler getHandler() {
return this.taskHandler;
}
public final int getTaskId() {
return this.taskHandler != null ? this.taskHandler.getTaskId() : -1;
}
public final void setHandler(TaskHandler taskHandler) {
if (this.taskHandler == null || taskHandler == null) {
this.taskHandler = taskHandler;
}
}
/**
* 这个任务被执行时,会调用的过程。<br>
* What will be called when the task is executed.
*
* @param currentTick 服务器从开始运行到现在所经过的tick数20ticks = 1秒1tick = 0.05秒。<br>
* The elapsed tick count from the server is started. 20ticks = 1second, 1tick = 0.05second.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract void onRun(int currentTick);
@Override
public final void run() {
this.onRun(taskHandler.getLastRunTick());
}
public void onCancel() {
}
public void cancel() {
try {
this.getHandler().cancel();
} catch (RuntimeException ex) {
Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex);
}
}
}
```
我们可以捕捉到几个关键信息,首先我们可以看到`onRun(int)`方法,它是一个抽象方法并且nk开发者很贴心的写上了注释示意我们这是Task中会被运行到代码块.
还有`onCancel()``cancel()`方法,onCancel()内部是一个空代码块,当Task被取消的时候(被任务调度器取消或者手动调用cancel()取消都会触发),这段代码块会被调用
比较常见的用途比如小游戏时间到的时候,一些判定胜负,给予奖励,关闭房间的一些代码可以用Task来实现或者调用.
我们还可以使用`cancel()`方法来强制取消一个Task,例如你想要写一个登录插件,每个玩家进入服务器时创建一个Task循环发送提示登录的信息,如果玩家输入了正确的密码并且登录成功才会取消掉这个Task.这时候就可以使用cancel方法了(仅举例,实际生产环境中,每个玩家进入创一个计时器是很费资源的,不会使用这种方法)
Handler是服务器调度Task所用到的一些东西,可以不懂,不妨碍计时器的使用.
开发者可以使用`PluginBase#getServer()`方法获取到Server对象,然后使用`Server#getSchedule()`获取到计时器的实例类.
```java
public class Main extends PluginBase{
@Override
public void onEnable(){
ServerScheduler scheduler = this.getServer().getScheduler();
}
}
```
获取到后,可以创建 __立即延时循环或延时循环__ 类型的计时器
立即(ServerSchedule#scheduleTask)
延时(ServerSchedule#scheduleDelayedTask)
循环(ServerSchedule#scheduleRepeatingTask)
延时循环(ServerSchedule#scheduleDelayedRepeatingTask)
计时器是需要一个被触发的时间间隔的,以tick为单位,如果是循环计时器,则是每次循环的时间,如果是延时,则是延迟多少tick才被触发,如果是延迟循环,则会要求填写延迟间隔与循环间隔.
举例将会在下一小节讲到
3.使用
下面会使用到`PluginTask`类,你需要对`PluginTask`类的方法做一个初步的了解,以及知道它继承了Task类.
```java
package cn.nukkit.scheduler;
import cn.nukkit.plugin.Plugin;
/**
* 插件创建的任务。<br>Task that created by a plugin.
*
* <p>对于插件作者,通过继承这个类创建的任务,可以在插件被禁用时不被执行。<br>
* For plugin developers: Tasks that extend this class, won't be executed when the plugin is disabled.</p>
*
* <p>另外,继承这个类的任务可以通过{@link #getOwner()}来获得这个任务所属的插件。<br>
* Otherwise, tasks that extend this class can use {@link #getOwner()} to get its owner.</p>
*
* 下面是一个插件创建任务的例子:<br>An example for plugin create a task:
* <pre>
* public class ExampleTask extends PluginTask&lt;ExamplePlugin&gt;{
* public ExampleTask(ExamplePlugin plugin){
* super(plugin);
* }
*
* {@code @Override}
* public void onRun(int currentTick){
* getOwner().getLogger().info("Task is executed in tick "+currentTick);
* }
* }
* </pre>
*
* <p>如果要让Nukkit能够延时或循环执行这个任务请使用{@link ServerScheduler}。<br>
* If you want Nukkit to execute this task with delay or repeat, use {@link ServerScheduler}.</p>
*
* @param <T> 这个任务所属的插件。<br>The plugin that owns this task.
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract class PluginTask<T extends Plugin> extends Task {
protected final T owner;
/**
* 构造一个插件拥有的任务的方法。<br>Constructs a plugin-owned task.
*
* @param owner 这个任务的所有者插件。<br>The plugin object that owns this task.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public PluginTask(T owner) {
this.owner = owner;
}
/**
* 返回这个任务的所有者插件。<br>
* Returns the owner of this task.
*
* @return 这个任务的所有者插件。<br>The plugin that owns this task.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final T getOwner() {
return this.owner;
}
}
```
我们可以通过创建一个继承PluginTask的类来创建一个PluginTask(为什么不直接继承Task类?,因为这个类添加了`getOwner()`方法,可以通过构造方法传入一份主类或任意你需要类实例的引用,方便编写代码)
```java
package net.noyark.NukkitLearn;
import cn.nukkit.scheduler.PluginTask;
public class NukkitLearnExampleTask extends PluginTask<Main> {
public NukkitLearnExampleTask(Main owner) {
super(owner);
}
@Override
public void onRun(int i) {
}
}
```
上面代码是一个简单Task类的样例,我们现在带入一个插件情境进一步说明计时器的用法.
> 小明是一个服主,他非常喜欢玩家来他的服务器玩并且希望玩家每玩45分钟能够停下来休息一下保护视力.于是他想要开发一个插件,当玩家单次进入服务器并且呆了45分钟后,能够发送一条信息提示(Player#sendMessage(String)玩家休息一会)
于是我们来头脑风暴一下,我们知道计时器有4种类型,不知道你还能不能想得起来
##### 立即(ServerSchedule#scheduleTask)
##### 延时(ServerSchedule#scheduleDelayedTask)
##### 循环(ServerSchedule#scheduleRepeatingTask)
##### 延时循环(ServerSchedule#scheduleDelayedRepeatingTask)
> 小明火速写好了一个Task类
```java
package net.noyark.NukkitLearn;
import cn.nukkit.Player;
import cn.nukkit.scheduler.PluginTask;
public class PresentationRestTask extends PluginTask<Main> {
private Player player;
public PresentationRestTask(Main owner, Player player) {
super(owner);
this.player = player;
}
@Override
public void onRun(int i) {
player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!");
}
}
```
> 以及一个主类
```java
package net.noyark.NukkitLearn;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.EventPriority;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
import cn.nukkit.plugin.PluginBase;
public class Main extends PluginBase implements Listener {
@EventHandler(priority = EventPriority.LOW,ignoreCancelled = true)
public void onJoin(PlayerJoinEvent event){
}
}
```
### 思考过程
* 使用立刻类型的计时器
- 使用立刻类型的计时器,在主类onJoin方法中填写入代码
> this.getServer().getScheduler().scheduleTask(new PresentationRestTask(this,event.getPlayer()));
> 小明找了个玩家小红进入服务器,小红刚进入服务器,左上角就被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!".
* 使用延迟类型的计时器
- 使用延时类型的计时器,在主类onJoin方法中填写入代码
> this.getServer().getScheduler().scheduleDelayedTask(new PresentationRestTask(this,event.getPlayer()),54000);
> 小明找了个玩家小红进入服务器,小红刚进入服务器45分钟后,左上角才被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!".
* 使用循环类型的计时器
- 似乎这很不符合我们的常理,并且增加了很多不必要的开销
- 修改Task,加入时间作为判断依据
>
```java
package net.noyark.NukkitLearn;
import cn.nukkit.Player;
import cn.nukkit.scheduler.PluginTask;
public class PresentationRestTask extends PluginTask<Main> {
private Player player;
private long stp;
public PresentationRestTask(Main owner, Player player,long stp) {
super(owner);
this.player = player;
this.stp = stp;
}
@Override
public void onRun(int i) {
if (System.currentTimeMillis() >= stp) {
player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!");
this.cancel();
}
}
}
```
- 使用循环类型的计时器,在主类onJoin方法中填写入代码
> this.getServer().getScheduler().scheduleRepeatingTask(new PresentationRestTask(this,event.getPlayer(),System.currentTimeMillis() + (45 * 60 * 1000)),60*20);
> 可以达成效果,但是实在是`太麻烦`
- 使用延时循环类型的计时器,在主类onJoin方法中填写入代码
> this.getServer().getScheduler().scheduleDelayedRepeatingTask(new PresentationRestTask(this,event.getPlayer()),45 * 60 * 20,0);
> 同时计时器也需要修改,否则就会45分钟一到就会无尽发送消息
>
```java
package net.noyark.NukkitLearn;
import cn.nukkit.Player;
import cn.nukkit.scheduler.PluginTask;
public class PresentationRestTask extends PluginTask<Main> {
private Player player;
public PresentationRestTask(Main owner, Player player) {
super(owner);
this.player = player;
}
@Override
public void onRun(int i) {
player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!");
this.cancel();
}
}
```
> 最终从各种角度来看,小明更应该使用`第二种`方法
[上一章](第二章-事件相关方法.md) [下一章](第四章-Server类和PluginManager类.md)

View File

@@ -0,0 +1,3 @@
[上一章](第一章-主要的事件介绍.md) [下一章](第三章-计时器的介绍.md)
[上一章](第一章-主要的事件介绍.md) [下一章](第三章-计时器的介绍.md)

View File

@@ -0,0 +1,224 @@
[上一章](第一章-如何搭建环境.md) [下一章](第三章-如何编写监听器.md)
# 第一部分 第二章 插件要素
参与编写者: MagicLu550
#### 建议学习时间: 50分钟
##### 学习要点: 了解nukkit的项目结构认识一些普通组件的定义和注册
插件要素这章会概括性的介绍nukkit的一些主要组件后面的章节将会对他们系统的介绍。
所有的插件式编程都有它的一套运行标准如maven插件,PocketMine插件以及我们的nukkit插件等。
我称插件的规则元素为一个**插件的要素**,主要是运行的主类所依托的父类或一个动作所依托的父类(PluginBase等),它
用于对服务器阅读的参数表(如web.xml,plugin.yml等),以及执行一系列动作的附件(如监听器,Servlet,过滤器,命令等)
nukkit的插件以**PluginBase**的代表,以plugin.yml为运行依据,以监听器等附件为动作,这些动作基于在PluginBase里
注册,所以nukkit的结构主要为这些
```
1. plugin.yml [必须有]
2. 继承于PluginBase的主类 [必须有]
3. Listener(监听器),Command(命令),Timer(计时器)
```
nukkit允许主类和监听器重叠,当我们制作简单的插件,为避免复杂,可以一个类担当主类和监听器的角色
并且我们也可以使用简单的方式来执行我们的命令。复杂项目中,我们不建议这么做
1. 注册一个监听器
```java
package net.noyark.www;
import cn.nukkit.event.Listener;
import cn.nukkit.plugin.PluginBase;
/**
* 实现一个Listener代表了一个监听器注册时监听器才真正生效
* 另外,Listener是一个接口我们需要实现(implements)它
*/
public class Example extends PluginBase implements Listener {
@Override
public void onEnable() {
//插件的管理由PluginManager实现后期会讲解这个组件
//listener为一个监听器对象这里我们将服务器事先生成的主类对象传入
//若要调用服务器生成的PluginBase对象我们必须要传入this而不是再new一个
//当然注册监听器时您可以再new一个但是不建议这么做
//第二个plugin对象是主类对象这个必须要传入this的
//可能有人疑惑,那在其他类里该怎么调用之前生成的对象呢?这个疑惑将会在接下来代码实现
this.getServer().getPluginManager().registerEvents(this,this);
}
}
```
如果跨类如何做这件事?
我们这里定义了一个Example类和一个OtherListener类。
OtherListener实现了Listener接口。
如图所示实现了跨类的注册
![2-01](images/2-01.png)
那么如何实现在监听器使用实现注册好的主类对象?
下面代码解决了这个问题要注意plugin = this这段代码
要定义在监听器对象被定义之前这样定义一个getPlugin方法就可以在监听器获取到了
```java
package net.noyark.www;
import cn.nukkit.plugin.PluginBase;
public class Example extends PluginBase{
private static Example plugin;
@Override
public void onEnable() {
plugin = this;
this.getServer().getPluginManager().registerEvents(new OtherListener(),this);
}
public static Example getPlugin() {
return plugin;
}
}
```
在监听器中调用
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e){
Example.getPlugin().getServer();
}
}
```
[监听器具体内容](第三章-如何编写监听器.md)将在后面讲解
2. 注册一个[命令](第四章-如何编写命令.md)
其实注册个命令就很简单,如何跨类等等和上面基本一样,但是主类是不能注册一个命令对象的
(因为命令是继承Command对象)
但是也有简便的方法
第一种:
[1]
```java
package net.noyark.www;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.plugin.PluginBase;
public class Example extends PluginBase{
private static Example plugin;
@Override
public void onEnable() {
plugin = this;
this.getServer().getPluginManager().registerEvents(new OtherListener(),this);
}
public static Example getPlugin() {
return plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
//这里返回true代表执行成功false则不成功
//sender 是发送命令的对象
//command 命令对象
// [1]
//label 是标签如果注册命令时label为null就会默认为命令名label的组成是fallBackPrefix+:+label
//label这个地方其实也不是很懂。一般注册时都是""或者null
//通过源码推测label和fallBackPrefix的组合是命令的唯一标识
//
//args 命令参数,比如/hello 1 2的参数为1和2,存储在数组中
//这里使用命令通过equals
//如何得到指令名称个人习惯原因
if("hello".equals(command.getName())){
//执行相关操作
return true;//最好加上
}
//这种方式虽然方便,但是命令多,且命令功能复杂时会难以维护
//少部分命令可以使用它
return true;
}
}
```
当然第一种方式命令要真正被监听我们还要在plugin.yml里声明
```yaml
permissions: #这个标签我们只写一次就ok了
plugin.hello:
description: 你好
default: op #权限组,后期会讲到
commands: #这个标签我们只写一次就ok了
hello:
description: 你好
usage: "/hello"
permission: plugin.hello
```
第二种是创建命令类
这个其实不用多说,和前面一样使用
```java
package net.noyark.www;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
public class MyCommand extends Command {
//hello为指令名字,后面description是它的介绍其他功能将后期讲解
public MyCommand() {
super("hello","一个测试指令");
}
@Override
public boolean execute(CommandSender commandSender, String label, String[] strings) {
return true;
}
}
```
注册很简单,第一个参数后面会讲解
```java
package net.noyark.www;
import cn.nukkit.plugin.PluginBase;
public class Example extends PluginBase{
@Override
public void onEnable() {
this.getServer().getCommandMap().register("",new MyCommand());
}
}
```
3.[计时器部分](第三章-计时器的介绍.md)我们将在后面讲解,它的体系较复杂一些
计时器主要形同java的Runnable线程池等等他们本质也是实现多线程。
也是开发者使用最广泛的一类组件。
4. 如何写plugin.yml
plugin.yml第一章提到过plugin.yml最主要的是
```yaml
name: FirstPlugin
main: net.noyark.www.Example
version: "0.0.1"
author: myName
api: ["1.0.8"]
description: My first plugin
```
snake1999曾说: 缺少他们的插件是不被nukkit运行的。
这些都是不能缺少的,在接下来章节中,将会系统的介绍里面常用的标签
目前从现在的维护者知悉api写什么其实并不重要因为nukkit向下兼容,不像
pocketMine一样变动很大,nukkit会淘汰掉过时的函数不会删除因此
可以保证老插件运行,但值得注意的是,不代表淘汰函数会完美运行,能
运行插件不代表不会出现bug但这也是相对pocketMine最方便的优势了。
[上一章](第一章-如何搭建环境.md) [下一章](第三章-如何编写监听器.md)

View File

@@ -0,0 +1,15 @@
# 第二部分 高级应用
本部分将讲解nukkit插件的高级应用部分学习完这一部分你就可以独自实现一个
自己的复杂的插件了,感谢您开始观看这个教程。
本部分分为七个章节
- [主要的事件的介绍](第一章-主要的事件介绍.md)
- [事件相关方法](第二章-事件相关方法.md)
- [计时器的介绍](第三章-计时器的介绍.md)
- [Server类和PluginManager类](第四章-Server类和PluginManager类.md)
- [各种实体类的方法介绍](第五章-各种实体类的方法介绍.md)
- [各种工具类的介绍](第六章-各种工具类的介绍.md)
- [如何发送数据包](第七章-如何发送数据包.md)
每个章节都具有关联性,必须看完第一个才可以进行下一个,这样才能条理的学习

View File

@@ -0,0 +1,3 @@
[上一章](第四章-Server类和PluginManager类.md) [下一章](第六章-各种工具类的介绍.md)
[上一章](第四章-Server类和PluginManager类.md) [下一章](第六章-各种工具类的介绍.md)

View File

@@ -0,0 +1,223 @@
[上一章](第四章-如何编写命令.md) [下一章](第六章-如何编写plugin.yml.md)
# 第一部分 第五章 如何编写配置文件
参与编写者: MagicLu550
#### 建议学习时间: 40分钟
##### 学习要点: 了解如何创建配置文件了解使用SimpleConfig
配置文件用于存储插件的配置信息,供用户自定义和修改,以及存储一些永久数据,我们称
配置文件属于 **持久层**
nukkit提供了多种配置文件格式如yaml,json,properties等其中最常用的是yaml,
我们主要讲解这个配置文件格式其他如果想要了解可以发送issues我们可以补充。
一. 关于yaml
nukkit的yaml框架基于[snakeYaml](https://github.com/bmoliveira/snake-yaml)实现的,
snakeYaml是一款使用广泛的yaml解析库,我们可以从它的[官网](https://yaml.org/type/index.html)了解
他们的语法在nukkit开发中我们更多使用key: value的映射形式
这是一个yaml的文件案例
```yaml
server:
name: 12
player:
- nihao
- xiaoming
- xiaogang
time:
year: 2019
```
yaml的标准语法是使用空格来划分级别前面为键后面为值且值和冒号之间有空格
虽然yaml的语法不止如此简单但是我们最常用的也就这些很简单的东西。
yaml的数组有两个表示形式
```yaml
array1: ["1","2"] #yaml的注释
array2: [1,2] #yaml不允许有重复的键
array3:
- 1
- 2
- 3
```
如果还要了解其他可以上yaml官网查看他们语法这里只讲解这些语法
二. 如何使用nukkit的配置文件库
原本的snakeYaml使用起来比较复杂因此nukkit官方提供了简化,同一
使用Config对象来表达和操作。
使用默认的配置
默认配置文件为config.yml,可以通过saveDefaultConfig()方法来实现,
前提是你的resources下面要创建一个config.yml,这个方法会默认在
plugins/${你的插件名字}下创建一个config.yml,并且会把resources下面的那个
config.yml内容复制过来.
![5-01](images/5-01.png)
之后我们调用这个方法即可
![5-02](images/5-02.png)
我们这里打开一个服务器,做一个实验可以看看,插件名为FirstPlugin
![5-03](images/5-03.png)
我们插件被加载成功,我们看看我们的文件夹
![5-04](images/5-04.png)
发现.../plugins/FirstPlugin下面出现了一模一样的config.yml
使用自定义的配置
自定义配置有两种方式,一种是已经初始化的文件,一种是空白文件,元素后期添加
1. 初始化的文件
saveResource(String fileName, boolean replace)
第一个是文件名称默认是this.getDataFolder()+"/"+fileName的路径
getDataFolder()为.../plugins/你的插件名 ,不包含后面的"/",使用时记得注意
假如你的插件名称为abc,那么路径为.../plugins/abc
第二个是是否替换文件每次重新启动服务器时会把文件内容重新更新如果为false,
则不会更新。或者已经存在config将不会换掉。如果为true那么就会换掉它。之前修改
的内容就会被替换(实际上加了true就是把文件删掉重新创建一遍)
而文件初始化的来源和之前的config.yml一样我们可以看看saveDefaultConfig()源码会发现,
它的本质也是saveResource
PluginBase.java
```
@Override
public void saveDefaultConfig() {
if (!this.configFile.exists()) {
this.saveResource("config.yml", false);
}
}
```
2. 空白文件
空白文件的文件默认没有getDataFolder(),它的默认路径是和nukkit.jar同一级别的文件目录.
```
Config config = new Config(this.getDataFolder()+"/myConfig.yml",Config.YAML);
config.save();//保存文件,文件不存在就会创建
```
Config可以实现对文件的操作,例如先前的config.yml,我们想对其实现操作,可以
```
Config config = new Config(this.getDataFolder()+"/config.yml",Config.YAML);
config.set("me","12");//将me修改为12,如果me不存在将创建me
config.save();
```
当然值得注意每次重新运行set都会修改一次me,所以如果手动修改了me就会被还原。
所以你可以做一个条件判断来避免还原的问题,比如判断文件是否存在。
当然这是初始化文件出现的问题我还是建议初始化文件使用saveResource来初始化
Config的常用方法
1.构造方法
我们可以看官方源码
Config.java
```
/**
* Constructor for Config instance with undefined file object
*
* @param type - Config type
*/
public Config(int type) {
this.type = type;
this.correct = true;
this.config = new ConfigSection();
}
/**
* Constructor for Config (YAML) instance with undefined file object
*/
public Config() {
this(Config.YAML);
}
public Config(String file) {
this(file, Config.DETECT);
}
public Config(File file) {
this(file.toString(), Config.DETECT);
}
public Config(String file, int type) {
this(file, type, new ConfigSection());
}
public Config(File file, int type) {
this(file.toString(), type, new ConfigSection());
}
```
file为文件名称默认路径为nukkit的根目录
type为类型主要使用的类型是
Config.java
```
public static final int DETECT = -1; //Detect by file extension
public static final int PROPERTIES = 0; // .properties
public static final int CNF = Config.PROPERTIES; // .cnf
public static final int JSON = 1; // .js, .json
public static final int YAML = 2; // .yml, .yaml
//public static final int EXPORT = 3; // .export, .xport
//public static final int SERIALIZED = 4; // .sl
public static final int ENUM = 5; // .txt, .list, .enum
public static final int ENUMERATION = Config.ENUM;
```
我们主要使用yaml,json.properties,其他大家可以自行查询。
如何实现动态内容
比如一些插件可以这样做
```yaml
message: ${player}加入了信息
```
其实可以使用replace做到
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class OtherListener implements Listener {
@EventHandler
public void onPLayerJoin(PlayerJoinEvent e){
String message;
//获取到内容后...
e.getPlayer().sendMessage(message.replace("${player}",e.getPlayer().getName()));
}
}
```
当然我们也可以基于反射做一个简单的解析工具
```java
class Utils{
String[] vals = {"player","ip"};
Map map = new HashMap(){
{
put("player","getName");
put("ip","getAddress");
}
};
public String translate(Event e,String message){
try{
Set<Map.Entry<String,String>> entries = map.entrySet();
for(Map.Entry<String,String> e1:entries){
message = message.replace("${"+e1.getKey()+"}",e.getClass().getDeclaredMethod(e1.getValue()).invoke(e).toString());
}
}catch (Exception e2){
e2.printStackTrace();
}
return message;
}
}
```
[上一章](第四章-如何编写命令.md) [下一章](第六章-如何编写plugin.yml.md)

View File

@@ -0,0 +1,199 @@
[上一章](第七章-PluginBase类.md) [下一章](第一章-主要的事件介绍.md)
# 第一部分 第八章 案例课:玩家进入案例演示
参与编写者: MagicLu550
#### 建议学习时间: 30分钟
##### 学习要点: 对于之前知识的巩固和加深认识
这个项目的需求很简单.实现一个玩家加入自定义提示信息,可以手动取消信息的功能
我们列举下步骤
- 创建项目
- 导入nukkit包
- 创建初始化配置文件set-info.yml
- 创建plugin.yml
- 创建主类
- 创建配置
- 创建监听器
- 读取配置内容
- 发送玩家信息
- 创建命令
-判断
- 注册监听器和命令
- 完成
1.第一到二步请自己完成
2.set-inf的内容
```yaml
message: 欢迎${player}加入了服务器
```
3.我的plugin.yml的内容
```yaml
name: MyJoin
main: net.noyark.www.MyJoin
version: "0.0.1"
author: magiclu550
api: ["1.0.9"]
description: My first plugin
#当采用注册时,控制权限需要在这里声明
commands:
set:
permission: myjoin.set
permissions:
myjoin.set:
default: op
```
4.创建主类 MyJoin
```java
package net.noyark.www;
import cn.nukkit.plugin.PluginBase;
public class MyJoin extends PluginBase {
private Map<String,Boolean> cancel;
private static MyJoin instance;
@Override
public void onLoad() {
this.getLogger().info("插件开始加载");
}
@Override
public void onEnable() {
instance = this;
this.saveDefaultConfig();
this.getLogger().info("插件初始化完毕");
}
@Override
public void onDisable() {
this.getLogger().info("插件已经关闭");
}
public static MyJoin getInstance() {
return instance;
}
public void setCancel(String name,boolean isCancel) {
cancel.put(name,isCancel);
}
public Map<String, Boolean> getCancel() {
return cancel;
}
}
```
5.创建监听器
```java
package net.noyark.www;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
public class MyJoinListener implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent e){
Boolean isCancel = MyJoin.getInstance().getCancel().get(e.getPlayer().getName());
if(isCancel!=null&&!isCancel) {
String message = MyJoin.getInstance().getConfig().getString("message");
e.setJoinMessage(message.replace("${player}", e.getPlayer().getName()));
}else{
MyJoin.getInstance().getCancel().put(e.getPlayer().getName(),true);
}
}
}
```
6.创建命令
```java
package net.noyark.www;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
public class MyCommand extends Command {
public MyCommand() {
super("set","设置是否改变加入信息","/set");
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if(args.length!=0)
MyJoin.getInstance().getCancel().put(sender.getName(),Boolean.valueOf(args[0]));
else
//这行代码可能会吓到你,不要担心,他只是一个找玩家名字来找到是否取消
//如果为空,则默认取消,如果不是空,则把玩家之前的状态取反
MyJoin.getInstance().getCancel().put(sender.getName(),
MyJoin.getInstance().getCancel().get(
sender.getName()
)==null?false:!MyJoin.getInstance()
.getCancel()
.get(sender.getName()
)
);
return true;
}
}
```
7. 注册
```java
package net.noyark.www;
import cn.nukkit.plugin.PluginBase;
import java.util.Map;
public class MyJoin extends PluginBase {
private Map<String,Boolean> cancel;
private static MyJoin instance;
@Override
public void onLoad() {
this.getLogger().info("插件开始加载");
}
@Override
public void onEnable() {
instance = this;
this.saveDefaultConfig();
//一定在它的后面注册!!否则会出现一系列奇怪问题...
this.getServer().getCommandMap().register("",new MyCommand());
this.getServer().getPluginManager().registerEvents(new MyJoinListener(),this);
this.getLogger().info("插件初始化完毕");
}
@Override
public void onDisable() {
this.getLogger().info("插件已经关闭");
}
public static MyJoin getInstance() {
return instance;
}
public void setCancel(String name,boolean isCancel) {
cancel.put(name,isCancel);
}
public Map<String, Boolean> getCancel() {
return cancel;
}
}
```
这大概是这个项目的流程,很简单,但是大家可以练一练,扎实基本功,我们马上开始推入下一章
感谢你们的支持
[上一章](第七章-PluginBase类.md) [下一章](第一章-主要的事件介绍.md)

View File

@@ -0,0 +1,3 @@
[上一章](第五章-各种实体类的方法介绍.md) [下一章](第七章-如何发送数据包.md)
[上一章](第五章-各种实体类的方法介绍.md) [下一章](第七章-如何发送数据包.md)

View File

@@ -0,0 +1,81 @@
[上一章](第五章-如何使用配置文件.md) [下一章](第七章-PluginBase类.md)
# 第一部分 第六章 如何编写plugin.yml
参与编写者: SmallasWater MagicLu550
#### 建议学习时间: 10分钟
##### 学习要点: 了解plugin.yml内容
1. 关于plugin.yml
plugin.yml 是 nukkit加载插件的主要文件 在加载插件前必先加载plugin.yml
在[第二章](第二章-插件要素.md)中已经介绍了其大致的结构
2. plugin.yml构成
```yaml
name: FirstPlugin # nukkit运行时识别的插件名
main: net.noyark.www.Example # 主类名称,不能以cn.nukkit开头
version: "0.0.1" # 版本号
author: 你的名字,这里指作者名称
api: ["1.0.9"] # 早期nukkit api为1.0.0
# 目前大概为1.0.9
depend: ["EconomyAPI"] # 依赖的插件名称 添加后如果Plugins文件夹不存在添加的插件则关闭本插件
loadbefore: ["EconomyAPI"] # 在xx插件之后加载 一般用作解决调用依赖库出现的ClassCastExpection
description: My first plugin # 介绍
commands: # Commands指令列表
fp: # 指令名称 不要加 /
usage: "/fp help" # 指令的用法 当onCommand返回false时 输出 usage内容
description: "指令介绍" # 指令的介绍
permission: FirstPlugin.fp # 指令权限 如果你希望插件仅允许 op执行 可以尝试这个
aliases: [] # 指令别名 可以增加中文名称
permissions:
FirstPlugin.fp: # 权限名称
description: "" # 权限的介绍
default: op # 权限限制 op / notop notop为非op可执行 op 为仅限op执行
```
3. 其他构成
我们通过拆解PluginDescription类可以知道
PluginDescription.java
```
private String name;
private String main;
private List<String> api;
private List<String> depend = new ArrayList<>();
private List<String> softDepend = new ArrayList<>();
private List<String> loadBefore = new ArrayList<>();
private String version;
private Map<String, Object> commands = new HashMap<>();
private String description;
private final List<String> authors = new ArrayList<>();
private String website;
private String prefix;
private PluginLoadOrder order = PluginLoadOrder.POSTWORLD;
```
其中load属性分为POSTWORLD和STARTUP他们区别官方在注释说明了
PluginLoadOrder.java
```
/**
* 表示这个插件在服务器启动时就开始加载。<br>
* Indicates that the plugin will be loaded at startup.
*
* @see cn.nukkit.plugin.PluginLoadOrder
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
STARTUP,
/**
* 表示这个插件在第一个世界加载完成后开始加载。<br>
* Indicates that the plugin will be loaded after the first/default world was created.
*
* @see cn.nukkit.plugin.PluginLoadOrder
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
POSTWORLD
```
config里有一个load,分别为STARUP与POSTWORLD,前者使插件加载在地图之前,后者为使插件加载在地图之后,如果对地图加载有需求的话必须填写POSTWORLD,否则将无法获取level
您也可以添加自己的网站: website属性
[上一章](第五章-如何使用配置文件.md) [下一章](第七章-PluginBase类.md)

View File

@@ -0,0 +1,97 @@
[上一章](第三章-计时器的介绍.md) [下一章](第五章-各种实体类的方法介绍.md)
# 第二部分 第四章 Server类和PluginManager类
参与编写者: MagicLu550
#### 建议学习时间: 20分钟
##### 学习要点: 了解Server类和PluginManager类
一. Server
1.概述
Server类是插件几乎所有接口的入口几乎一切的接口都是基于这个类获得的,而在nukkit中,Server类
是作为一个对象单独存在,Server的实例化意味着nukkit服务器的启动,并且不允许外部调用其构造方法,但可以根据getInstance方法或者插件主类提供的
getServer方法可以获得,这里我们提到了两个获得Server的方法
```
Server.getInstance();
this.getServer();//this.getClass() == mainClass
```
Server对象是在Nukkit类里完成初始化的,所以我们不需要担心Server对象是否存在的问题.加载插件
前,Server对象已经存在.一切启动的初始化操作都在Server的构造方法中完成.对于Server的复杂原理,
这里不过多赘述,可以参见第四部分的内容
Nukkit.java Line. 108-115
```
try {
if (TITLE) {
System.out.print((char) 0x1b + "]0;Nukkit is starting up..." + (char) 0x07);
}
new Server(PATH, DATA_PATH, PLUGIN_PATH, language);
} catch (Throwable t) {
log.throwing(t);
}
```
2. Server类的常用方法
* addOp(String) 可以添加op管理员的名称,name是玩家名称
* addWhitelist(String) 可以添加白名单
* batchPackets(Player[], DataPacket[]) 批量发送数据包,后面[数据包发送篇](第七章-如何发送数据包.md)详细讲解
* broadMessage(String) 发送服务器广播信息,所有玩家可见
* addRecipe(Recipe) 添加配方,这个配方指包括合成、炉子、炼药台等使用的配方。
* broadcastPacket(Player[], DataPacket) 向所有玩家广播数据包
* forceShutdown() 强制关闭服务端
* doAutoSave() 自动保存
* generateLevel(String) 产生一个level(世界),String为名字,返回创建是否成功
* getAllowFlight() 获得这个服务器是否是允许飞行的
* getApiVersion() 获取插件api的版本
* getCommandAliases() 将返回以(一个指令名对应着多个别名)为一对的Map集合
* getCommandMap() 获取指令Map,通过它可以注册一些命令,[前面已经说到过](第四章-如何编写命令.md)
* getDataPath() 获取服务端的数据目录
* getDefaultGamemode() 获取服务端的默认模式(如创造模式等)
* getDefaultLevel() 获取默认的世界对象,如World
* getDifficulty() 获得游戏难度,如和平模式等
* getIp() 获得服务端的ip地址
* getIpBans() 获得封禁的信息(ban)
* getLanguage() 获得服务端默认语言(如zh等)
* getLevelByName(String) 通过世界名称获得世界对象
* getMaxPlayers() 获得最大人数
* getMotd() 获得服务端的motd
* getName() 获得服务端的名称
* getNameBans() 获得迸封禁的玩家表
* getOfflinePlayer(String) 通过玩家名称得到不在线玩家
* getOnlinePlayers() 获得在线玩家,UUID都是唯一的标识符
* getPlayerExact(String) 通过名称获得一个确切的玩家​
* getPluginManager() 获得插件管理器
* getOps() 获得管理员清单
* getPort() 获得端口
* getPluginPath() 获得插件文件夹的位置
* getScheduler() 获得任务表,可以注册有关多线程之类的东西
* getSubMotd() 获得附属的motd
* hasWhitelist() 是否有白名单
* isOp(String) 判断一个玩家是否是op,String为玩家名称
* reload() 重启服务端
* reloadWhitelist() 重新加载白名单
* removeOnlinePlayer(Player) 删除在线玩家
* removeOp(String) 删除指定管理员
* removeWhitelist(String) 删除一个玩家的白名单
* shutdown() 关闭服务端
* unloadLevel(Level) 卸载一个世界
二. PluginManager
1. 概述
PluginManager是插件管理器,很多的插件加载和数据储存都在这里进行,如监听器
的注册等
使用这个类也可以实现动态加载插件等一系列的操作,PluginManager的加载基于JavaPluginLoader
2. 常用的方法
* addPermission(Permission) 添加Permission对象
* callEvent(Event) 触发一个事件
* clearPlugins() 清空插件
* disablePlugin(Plugin) 停止一个插件这个插件会提前调用onDisable
并卸载
* getPlugin(String) 得到其他插件的插件对象
* loadPlugin(File) 加载一个插件,路径默认为服务端根目录
* loadPlugins(File) 加载一个文件夹的插件
* registerEvents(Listener, Plugin) 注册监听器
* removePermission(String) 删除一个Permission
[上一章](第三章-计时器的介绍.md) [下一章](第五章-各种实体类的方法介绍.md)

View File

@@ -0,0 +1,237 @@
[上一章](第三章-如何编写监听器.md) [下一章](第五章-如何使用配置文件.md)
# 第一部分 第四章 如何编写命令
参与编写者: MagicLu550
#### 建议学习时间: 30分钟
##### 学习要点: 学习自己创建一个简易的命令,了解指令和指令映射,了解SimpleCommand的使用
其实创建一个简易的命令很简单,创建的过程也不必多讲,先前我在
[第二章](第二章-插件要素.md)已经提到了如何创建它,大家可以参考
nukkit的原生命令也是很多基于Command创建的.很多项目是需求指令
以使得用户和您的项目操作,也就是作为一个 **接口(Interface)** ,
接下来在基于[第二章](第二章-插件要素.md)的命令注册后,我们介绍
以下nukkit的命令。
nukkit的命令的父类是Command它有很多的构造方法
```
public Command(String name) {
this(name, "", null, new String[0]);
}
public Command(String name, String description) {
this(name, description, null, new String[0]);
}
public Command(String name, String description, String usageMessage) {
this(name, description, usageMessage, new String[0]);
}
public Command(String name, String description, String usageMessage, String[] aliases) {
this.commandData = new CommandData();
this.name = name.toLowerCase(); // Uppercase letters crash the client?!?
this.nextLabel = name;
this.label = name;
this.description = description;
this.usageMessage = usageMessage == null ? "/" + name : usageMessage;
this.aliases = aliases;
this.activeAliases = aliases;
this.timing = Timings.getCommandTiming(this);
this.commandParameters.put("default", new CommandParameter[]{new CommandParameter("args", CommandParamType.RAWTEXT, true)});
}
```
我们可以知道nukkit的命令都是toLowerCase的即小写也就是说命令不区分大小写。
对于命令如何存储,服务端如何识别一个命令,我们从源码中找到以下资料:
--
命令的区分标识是fallBackPrefix+:+label默认为指令的名称一般fallBackPrefix都写""
这一块知识缺点将会有其他人补充[1]
代码依据 - SimpleCommandMap.java
```
159 private boolean registerAlias(Command command, boolean isAlias, String fallbackPrefix, String label) {
160 this.knownCommands.put(fallbackPrefix + ":" + label, command);
```
--
存储指令的容器是实现CommandMap接口的即SimpleCommandMap,我们可以通过
```
this.getServer().getCommandMap();
```
得到CommandMap.
Command,CommandMap有很多方法
1. Command
1. 根构造方法的参数为
```
String name, String description, String usageMessage, String[] aliases
第一个name是指令名称最终会转换为全小写
第二个description是指令介绍用于给玩家查看使用的
第三个usageMessage就是当玩家对命令使用错误返回的信息
第四个aliases就是指令的别名指令可以有多个别名
```
当然,也有简化的构造方法,可以根据你的需求任意调用,这里不多阐述,其他的都是为默认值
2. Command的主要属性
如同command的构造方法一样,command我们需要了解的属性基本也就是这四个。其他属性将会
在nukkit原理解析的时候讲解
3. Command的比较常用的方法
1. boolean execute(CommandSender commandSender, String label, String[] strings)
这个方法是需要开发者自行实现的方法当指令被触发就会执行execute里的代码
它的参数我们在[第二章](第二章-插件要素.md)提到了
2. String getName()
这个可以获取指令的名称
其余方法我们将会在后期附件讲解到,如果有想要知悉的其他方法,我们会另外在这里做补充,
或者您认为常用的也可以pull request添加进去
2. CommandMap
1. boolean register(String fallbackPrefix, Command command)
可以注册指令fallbackPrefix是前缀用于服务端存储命令对象的标识
nukkit的本地命令的fallbackPrefix为nukkit
command则为你的自定义命令对象
2. void registerAll(String fallbackPrefix, List<? extends Command> commands)
这个可以一次性注册多个指令
3. boolean dispatch(CommandSender sender, String cmdLine)
这个调用一个命令cmdLine就是日常所输入的命令
4. void registerSimpleCommands(Object object)
这个是调用简单指令,通过注解实现的指令对象,我们后面将会演示如何使用它。
nukkit官方后来推出一系列简化操作如SimpleCommand,SimpleConfig等我们这里解释以下SimpleCommand
SimpleCommand运用了注解同样通过 **反射** 实现的,我们可以看到官方的源码来探讨它的使用
SimpleCommandMap.java
```
@Override
public void registerSimpleCommands(Object object) {
for (Method method : object.getClass().getDeclaredMethods()) {
cn.nukkit.command.simple.Command def = method.getAnnotation(cn.nukkit.command.simple.Command.class);
if (def != null) {
SimpleCommand sc = new SimpleCommand(object, method, def.name(), def.description(), def.usageMessage(), def.aliases());
Arguments args = method.getAnnotation(Arguments.class);
if (args != null) {
sc.setMaxArgs(args.max());
sc.setMinArgs(args.min());
}
CommandPermission perm = method.getAnnotation(CommandPermission.class);
if (perm != null) {
sc.setPermission(perm.value());
}
if (method.isAnnotationPresent(ForbidConsole.class)) {
sc.setForbidConsole(true);
}
CommandParameters commandParameters = method.getAnnotation(CommandParameters.class);
if (commandParameters != null) {
Map<String, CommandParameter[]> map = Arrays.stream(commandParameters.parameters())
.collect(Collectors.toMap(Parameters::name, parameters -> Arrays.stream(parameters.parameters())
.map(parameter -> new CommandParameter(parameter.name(), parameter.type(), parameter.optional()))
.distinct()
.toArray(CommandParameter[]::new)));
sc.commandParameters.putAll(map);
}
this.register(def.name(), sc);
}
}
}
```
很显然,简易命令必须要有@Command注解在方法上,方法上标记一些内容,当然,最终只是把一个类拆解,分为多个命令
对象注册(SimpleCommand),最终也继承自Command。SimpleCommand提供了对于参数最大和最小的限制。
SimpleCommand.java
```
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if (this.forbidConsole && sender instanceof ConsoleCommandSender) {
this.sendInGameMessage(sender);
return false;
} else if (!this.testPermission(sender)) {
return false;
} else if (this.maxArgs != 0 && args.length > this.maxArgs) {
this.sendUsageMessage(sender);
return false;
} else if (this.minArgs != 0 && args.length < this.minArgs) {
this.sendUsageMessage(sender);
return false;
}
boolean success = false;
try {
//这里执行我们的命令
success = (Boolean) this.method.invoke(this.object, sender, commandLabel, args);
} catch (Exception exception) {
Server.getInstance().getLogger().logException(exception);
}
if (!success) {
this.sendUsageMessage(sender);
}
return success;
}
```
这段代码我们可以知道方法的参数有规范要求的
Object object,CommandSender sender,String label,String[] args
object就是我们的命令对象了,通过registerSimpleCommand注册进去的命令对象
其他显而易见,不再多讲,具体如何使用,其实很简单
```java
package net.noyark.www;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.simple.Arguments;
import cn.nukkit.command.simple.Command;
public class MySimpleCommand {
@Command(name = "hello",description = "233",usageMessage = "/hello")
@Arguments(max = 10,min = 0)
public void onHelloCommand(Object object, CommandSender sender, String label, String[] args){
//这里写指令处理代码
}
}
```
最终通过registerSimpleCommand注册即可.事实上是对Command的封装
command的用户组(这里参考自snake1999的文章)
```yaml
permissions: #这个标签我们只写一次就ok了
plugin.hello:
description: 你好
default: op #权限组,后期会讲到
commands: #这个标签我们只写一次就ok了
hello:
description: 你好
usage: "/hello"
permission: plugin.hello
```
这里我们发现了default这个的选项有以下几种
- op代表服务器管理员在ops.txt中规定。
- notop代表除服务器管理员外的所有玩家。
- true代表所有玩家。
- false代表空集。如果某个命令对应这个权限那就没有人能够使用这个命令控制台除外
大家可以根据这些选项来控制指令的使用范围了
参考文献:
- [Nukkit插件从0开始](https://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html)
[上一章](第三章-如何编写监听器.md) [下一章](第五章-如何使用配置文件.md)

View File

@@ -0,0 +1,20 @@
```bash
docker run -d \
--name filecodebox \
--restart=always \
-p 12345:12345 \
-v /shumengya/docker/storage/filecodebox:/app/data \
lanol/filecodebox:beta
```
```bash
#典型的非关系型数据库json
docker pull lanol/filecodebox:beta
```
```
docker stop lanol/filecodebox:beta
docker rm lanol/filecodebox:beta
```

View File

@@ -0,0 +1,32 @@
```bash
docker run -d \
--name ntfy \
--restart=always \
-e TZ="Asia/Shanghai" \
-e NTFY_BASE_URL="https://ntfy.shumengya.top" \
-e NTFY_CACHE_FILE="/var/cache/ntfy/cache.db" \
-e NTFY_AUTH_FILE="/var/lib/ntfy/auth.db" \
-e NTFY_AUTH_DEFAULT_ACCESS="deny-all" \
-e NTFY_BEHIND_PROXY="true" \
-e NTFY_ATTACHMENT_CACHE_DIR="/var/lib/ntfy/attachments" \
-e NTFY_ENABLE_LOGIN="true" \
-v /shumengya/docker/storage/ntfy/cache:/var/cache/ntfy \
-v /shumengya/docker/storage/ntfy/etc:/etc/ntfy \
-v /shumengya/docker/storage/ntfy/lib:/var/lib/ntfy \
-p 82:80 \
binwiederhier/ntfy:latest \
serve
```
```bash
#典型的非关系型数据库json
docker pull binwiederhier/ntfy:latest
```
```
docker stop binwiederhier/ntfy:latest
docker rm binwiederhier/ntfy:latest
```

View File

@@ -0,0 +1,14 @@
```shell
git remote add origin <url>
git remote add origin https://github.com/shumengya6666666/Pixel-plane-wars.git
git remote add origin git@github.com:shumengya6666666/Pixel-plane-wars.git
git remote set-url origin git@github.com:shumengya6666666/Pixel-plane-wars.git
git clone git@github.com:shumengya6666666/Pixel-plane-wars.git
git remote add origin git@github.com:shumengya6666666/Pixel-Odyssey.git
git push -u origin main
git remote add origin git@github.com:shumengya6666666/Sprout-Farm.git
git remote set-url origin git@github.com:shumengya6666666/Sprout-Farm.git
git push -u origin master
git remote add origin git@github.com:shumengya6666666/Pixel-plane-wars.git
```

View File

@@ -0,0 +1,22 @@
打开终端tmoe面板
curl -LO https://gitee.com/mo2/linux/raw/2/2.awk
awk -f 2.awk
ubuntu和deepin系统安装宝塔面板
wget -O install.sh https://download.bt.cn/install/install_lts.sh && sudo bash install.sh ed8484bec
重启宝塔面板:
bt restart
开启宝塔面板:
bt default
安装和运行casaos轻量nas系统
curl -fsSL https://get.casaos.io | sudo bash
安装和运行1panel面板
curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh
连接ssh示例
ssh root@192.168.25.151 -p 6003

View File

@@ -0,0 +1,50 @@
Linux scp 命令总结
用途:安全复制文件/目录到本地或远程主机(基于 SSH 协议)。
基本语法
scp [选项] 源文件 目标路径
 
常用选项
选项 说明
 -r  递归复制目录及子目录
 -P <port>  指定 SSH 端口(注意大写 P
 -i <identity_file>  使用指定的私钥文件认证
 -C  压缩传输数据
 -p  保留文件属性(时间戳、权限等)
 -v  显示详细传输过程
典型示例
1. 本地文件 → 远程主机
scp local_file.txt user@remote_host:/path/to/remote_dir/
 
2. 远程文件 → 本地
scp user@remote_host:/path/to/remote_file.txt ~/local_dir/
 
3. 复制目录(需加  -r 
scp -r local_dir/ user@remote_host:/path/to/remote_dir/
 
4. 远程主机间复制(通过本地中转)
scp user1@host1:/file.txt user2@host2:/path/
 
5. 指定端口(如非默认 22 端口)
scp -P 2222 local_file.txt user@remote_host:/remote_path/
 
6. 使用私钥认证
scp -i ~/.ssh/id_rsa local_file.txt user@remote_host:/remote_path/
 
注意事项
- 远程路径需以  user@host:  开头。
- 目标路径不存在时会自动创建(目录需  -r 
- 若提示权限问题,检查私钥文件权限(需  chmod 600 

View File

@@ -0,0 +1,27 @@
在 Linux 中解压不同格式的压缩文件,可使用以下命令:
1. tar.gz 文件
tar -xzf filename.tar.gz
 
- 说明:
 x  表示解压 z  调用 gzip 解压 f  指定文件名。
2. tar.zst 文件
tar --zstd -xf filename.tar.zst
 
- 说明:
 --zstd  明确使用 zstd 解压 x  解压 f  指定文件名。需确保系统已安装  zstd  工具。
3. tar.xz 文件
tar -xJf filename.tar.xz
 
- 说明:
 J  调用 xz 解压 x  解压 f  指定文件名。
补充:若需解压时显示详细过程,可添加  -v  参数(如  tar -xvzf filename.tar.gz 

View File

@@ -0,0 +1,127 @@
在 Linux 系统中,让服务一直保持在后台运行有多种方法,下面为你详细介绍:
### 1. 使用 `&` 符号
在命令后面加上 `&` 符号可以让命令在后台运行。
#### 示例
```bash
python my_script.py &
```
上述命令会让 `my_script.py` 脚本在后台运行。
#### 注意事项
- 这种方式虽然简单,但当你关闭当前终端会话时,该进程可能会收到 `SIGHUP` 信号而终止。
- 你可以使用 `jobs` 命令查看当前终端中后台运行的作业,使用 `fg` 命令将后台作业调回到前台。
### 2. 使用 `nohup` 命令
`nohup` 命令可以让程序忽略 `SIGHUP` 信号,从而在你关闭终端后继续运行。
#### 示例
```bash
nohup python my_script.py > output.log 2>&1 &
```
- `nohup`:用于忽略 `SIGHUP` 信号。
- `> output.log`:将标准输出重定向到 `output.log` 文件。
- `2>&1`:将标准错误输出也重定向到标准输出,即同样写入 `output.log` 文件。
- `&`:让命令在后台运行。
#### 查看运行状态
使用 `ps` 命令可以查看进程是否还在运行:
```bash
ps -ef | grep my_script.py
```
### 3. 使用 `screen` 工具
`screen` 是一个全屏窗口管理器,它可以创建多个虚拟终端会话,即使你断开连接,会话中的进程仍然会继续运行。
#### 安装 `screen`
在 Debian 或 Ubuntu 系统上:
```bash
sudo apt-get install screen
```
在 CentOS 或 RHEL 系统上:
```bash
sudo yum install screen
```
#### 使用步骤
1. 启动一个新的 `screen` 会话:
```bash
screen
```
2.`screen` 会话中运行你的命令,例如:
```bash
python my_script.py
```
3. 按下 `Ctrl + A` 然后再按下 `D` 组合键,将当前 `screen` 会话分离,此时会话会在后台继续运行。
4. 如果你想重新连接到该会话,可以使用以下命令:
```bash
screen -r
```
### 4. 使用 `tmux` 工具
`tmux` 也是一个类似 `screen` 的终端复用工具,功能更强大。
#### 安装 `tmux`
在 Debian 或 Ubuntu 系统上:
```bash
sudo apt-get install tmux
```
在 CentOS 或 RHEL 系统上:
```bash
sudo yum install tmux
```
#### 使用步骤
1. 启动一个新的 `tmux` 会话:
```bash
tmux new -s mysession
```
这里的 `mysession` 是会话的名称,你可以自定义。
2.`tmux` 会话中运行你的命令,例如:
```bash
python my_script.py
```
3. 按下 `Ctrl + B` 然后再按下 `D` 组合键,将当前 `tmux` 会话分离。
4. 如果你想重新连接到该会话,可以使用以下命令:
```bash
tmux attach -t mysession
```
### 5. 使用系统服务管理工具(如 `systemd`
对于需要长期稳定运行的服务,推荐使用系统服务管理工具,如 `systemd`(大多数现代 Linux 发行版都使用 `systemd`)。
#### 创建服务文件
创建一个新的服务文件,例如 `/etc/systemd/system/my_script.service`
```ini
[Unit]
Description=My Python Script Service
After=network.target
[Service]
ExecStart=/usr/bin/python /path/to/my_script.py
WorkingDirectory=/path/to/working/directory
Restart=always
User=your_username
[Install]
WantedBy=multi-user.target
```
- `Description`:服务的描述信息。
- `After`:指定服务在网络服务启动后再启动。
- `ExecStart`:指定要执行的命令。
- `WorkingDirectory`:指定工作目录。
- `Restart`:设置为 `always` 表示服务崩溃或停止后会自动重启。
- `User`:指定运行服务的用户。
#### 启动和管理服务
```bash
# 重新加载 systemd 管理器配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start my_script.service
# 设置服务开机自启
sudo systemctl enable my_script.service
# 查看服务状态
sudo systemctl status my_script.service
```

View File

@@ -0,0 +1,36 @@
在 Linux 中,可以使用 `cp` 命令将某个路径的文件复制到另一个位置。命令格式如下:
```bash
cp 源文件路径 目标路径
```
### 示例
假设要将 `/home/user/file.txt` 复制到 `/home/user/documents/`,命令如下:
```bash
cp /home/user/file.txt /home/user/documents/
```
### 选项
- **`-r`**:复制目录及其内容。
- **`-i`**:覆盖前提示确认。
- **`-v`**:显示复制进度。
### 示例
1. **复制目录**
```bash
cp -r /home/user/folder /home/user/documents/
```
2. **覆盖前提示**
```bash
cp -i /home/user/file.txt /home/user/documents/
```
3. **显示进度**
```bash
cp -v /home/user/file.txt /home/user/documents/
```
### 总结
`cp` 命令用于复制文件或目录,常用选项包括 `-r`、`-i` 和 `-v`。

View File

@@ -0,0 +1,5 @@
apt install tur-repo
apt install ollama
ollama serve
重新开启个会话
安装各种开源模型

View File

@@ -0,0 +1,17 @@
在Debian系统中可以通过以下几种方法查看使用的无线网卡的名称
### 使用命令行工具`ifconfig`
1. 打开终端,输入命令`ifconfig -a`
2. 该命令会列出系统中所有的网络接口,包括有线网卡、无线网卡等。通常无线网卡的名称会以`wlan`开头,如`wlan0``wlan1`等。你可以根据网卡的相关信息如MAC地址、IP地址等来确定具体使用的无线网卡。
### 使用`iwconfig`命令
1. 在终端中输入`iwconfig`
2. 此命令主要用于配置无线网卡,它会显示出系统中的无线接口信息,包括无线网卡的名称、频率、信号强度等。通过识别无线网卡的相关参数,能找到正在使用的无线网卡名称。
### 查看`/sys/class/net/`目录
1. 打开终端,输入命令`ls /sys/class/net/`
2. 该目录下列出了系统中所有的网络设备接口文件,无线网卡的名称通常也以`wlan`开头,通过查看该目录可以找到无线网卡的名称。
### 使用`nmcli`命令
1. 确保`NetworkManager`服务已安装并运行,然后在终端中输入`nmcli device status`
2. 该命令会显示系统中网络设备的状态信息,包括设备名称、设备类型、状态等。在设备类型为`wifi`的行中,可以找到无线网卡的名称。

View File

@@ -0,0 +1,40 @@
在 Linux 系统中,安装的软件包通常会放置在以下几个目录中,具体路径取决于软件包的类型和安装方式。
### 1. **二进制文件(可执行文件)**
- **`/usr/bin/`**:大多数用户安装的软件的二进制文件都会安装在此目录下。这些是常见的应用程序和命令(例如 `ls`, `cp`, `git` 等)。
- **`/bin/`**:存放一些最基本的系统命令,这些命令在系统的任何时候都需要可用,通常在恢复模式下也能使用。
- **`/usr/local/bin/`**:用户自己安装的软件(通常是通过源代码编译安装的软件)会放在这个目录下。它优先级高于 `/usr/bin/`,因此用户安装的命令会覆盖系统自带的软件。
- **`/sbin/``/usr/sbin/`**:这些目录通常存放的是系统管理工具,需要管理员权限来使用。比如 `ifconfig`, `mount` 等。
### 2. **库文件**
- **`/lib/`**:包含了系统启动和运行时所必需的共享库(例如 C 库)。
- **`/usr/lib/`**:用于存放大多数安装的软件包的共享库,支持应用程序的运行。
- **`/usr/local/lib/`**:与 `/usr/local/bin/` 类似,这是存放用户手动安装的程序和库的地方。
### 3. **配置文件**
- **`/etc/`**:这是系统和应用程序的全局配置文件所在的目录。几乎所有的配置文件都可以在这里找到。例如 `/etc/passwd` 存放用户信息,`/etc/ssh/sshd_config` 存放 SSH 配置等。
- **`/usr/local/etc/`**:对于用户自己安装的软件包,它的配置文件一般会放在此目录下。
### 4. **文档和帮助文件**
- **`/usr/share/`**:存放共享的数据文件,包括应用程序的文档、帮助文件、图标等。例如 `/usr/share/doc/` 目录存放着已安装软件包的文档和许可证。
- **`/usr/local/share/`**:用户自己安装的软件的共享数据文件通常存放在此目录。
### 5. **临时文件**
- **`/tmp/`**:这个目录用于存放临时文件,系统或应用程序运行时会使用这个目录来存放临时数据。通常,`/tmp/` 目录中的文件会在系统重启后被删除。
- **`/var/tmp/`**:类似于 `/tmp/`,但 `/var/tmp/` 中的文件不会在系统重启时被清除,适用于需要较长时间存储的临时文件。
### 6. **日志文件**
- **`/var/log/`**:存放系统和应用程序的日志文件。比如 `/var/log/syslog` 记录了系统日志,`/var/log/auth.log` 记录了认证相关的日志等。
### 7. **其他相关目录**
- **`/opt/`**:这个目录通常用于存放大型的第三方软件包,特别是那些由供应商提供的独立应用程序(例如 Google Chrome 或 Oracle Java通常会安装在这个目录。
- **`/home/`**:每个用户的个人目录会在 `/home/` 目录下(例如 `/home/user/`),其中可能包含用户安装的软件或自定义的程序。
### 总结
- **二进制文件**`/bin/``/usr/bin/``/usr/local/bin/`
- **库文件**`/lib/``/usr/lib/``/usr/local/lib/`
- **配置文件**`/etc/``/usr/local/etc/`
- **共享数据文件**`/usr/share/``/usr/local/share/`
- **日志文件**`/var/log/`
- **临时文件**`/tmp/``/var/tmp/`
- **软件包**`/opt/`(大型第三方应用)

View File

@@ -0,0 +1,9 @@
输入 xvfb-run -a qq --no-sandbox 命令启动。
保持后台运行 请输入 screen -dmS napcat bash -c "xvfb-run -a qq --no-sandbox"
后台快速登录 请输入 screen -dmS napcat bash -c "xvfb-run -a qq --no-sandbox -q QQ号码"
Napcat安装位置 /opt/QQ/resources/app/app_launcher/napcat
WEBUI_TOKEN 请自行查看/opt/QQ/resources/app/app_launcher/napcat/config/webui.json文件获取
注意, 您可以随时使用 screen -r napcat 来进入后台进程并使用 ctrl + a + d 离开(离开不会关闭后台进程)。
停止后台运行
screen -S napcat -X quit

View File

@@ -0,0 +1,202 @@
配置 SSH 服务端(`sshd_config`)是确保服务器安全性和功能性的关键步骤。以下是基于您提供的默认配置文件的详细配置指南,包括关键选项的解释和安全建议。
### 1. 基本配置
#### 端口设置
默认情况下SSH 使用端口 22。为了提高安全性您可以更改为其他端口。
```bash
Port 22
```
**建议**:修改为非标准端口(如 `Port 2222`)可以减少被自动扫描攻击的风险。
```bash
Port 2222
```
#### 监听地址
默认配置监听所有 IPv4 地址。
```bash
ListenAddress 0.0.0.0
#ListenAddress ::
```
**建议**:如果服务器有多个网络接口,您可以指定特定的 IP 地址来监听。例如,仅监听内部网络:
```bash
ListenAddress 192.168.1.100
```
### 2. 主机密钥
主机密钥用于验证服务器的身份。确保这些密钥存在并且安全。
```bash
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
```
**建议**:取消注释需要的密钥类型,确保至少启用一种强加密算法(如 Ed25519
```bash
HostKey /etc/ssh/ssh_host_ed25519_key
```
### 3. 身份验证
#### 禁止 root 登录
出于安全考虑,建议禁止 root 用户通过 SSH 登录。
```bash
#PermitRootLogin prohibit-password
```
**建议**:将其设置为 `no``prohibit-password`,推荐使用密钥认证。
```bash
PermitRootLogin no
```
#### 密码认证
启用或禁用密码认证。为了更高的安全性,建议使用密钥认证并禁用密码认证。
```bash
#PasswordAuthentication yes
PasswordAuthentication no
```
#### 公钥认证
确保启用公钥认证,并配置 `AuthorizedKeysFile`
```bash
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
```
**建议**:保持默认配置,确保每个用户的 `~/.ssh/authorized_keys` 文件中包含正确的公钥。
#### 键盘交互认证
如果只使用密钥认证,禁用键盘交互认证。
```bash
KbdInteractiveAuthentication no
```
### 4. 使用 PAM
PAM可插拔认证模块用于增强认证机制。
```bash
UsePAM yes
```
**建议**:保持启用,但确保其他认证方法(如密码认证)根据需要进行配置。
### 5. 限制用户和组
通过 `AllowUsers``AllowGroups` 限制可以通过 SSH 登录的用户或组。
```bash
# Allow only specific users
#AllowUsers user1 user2
# Allow only specific groups
#AllowGroups sshusers
```
**建议**:根据需要取消注释并配置,限制访问范围。
```bash
AllowUsers alice bob
```
### 6. 其他安全设置
#### 禁用空密码登录
```bash
#PermitEmptyPasswords no
PermitEmptyPasswords no
```
**确保**:禁止使用空密码登录。
#### 启用防火墙和限制连接数
配置防火墙(如 `ufw``iptables`)仅允许必要的端口和 IP 地址访问。同时,可以设置 `MaxAuthTries``MaxSessions` 来限制尝试次数和并发会话数。
```bash
MaxAuthTries 3
MaxSessions 2
```
### 7. 日志和监控
设置合适的日志级别以监控 SSH 活动。
```bash
LogLevel VERBOSE
```
**建议**:根据需要设置日志级别,如 `INFO``VERBOSE`,并定期检查日志文件(通常位于 `/var/log/auth.log``/var/log/secure`)。
### 8. 重启 SSH 服务
在完成配置后,重新启动 SSH 服务以应用更改。
```bash
sudo systemctl restart sshd
```
**检查配置是否正确**
在重新启动之前,最好检查配置文件是否有语法错误:
```bash
sudo sshd -t
```
如果没有输出,说明配置语法正确。否则,按照错误提示进行修正。
### 9. 其他推荐配置
- **启用双因素认证2FA**:增加登录的安全性。
- **限制 SSH 登录时间**:通过 `sshd_config` 或使用其他工具限制用户的登录时间。
- **使用 Fail2Ban**:防止暴力破解攻击,通过监控日志文件并动态更新防火墙规则。
### 示例优化后的 `sshd_config`
以下是根据上述建议优化后的配置示例:
```bash
Port 2222
ListenAddress 0.0.0.0
HostKey /etc/ssh/ssh_host_ed25519_key
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
AllowUsers alice bob
LogLevel VERBOSE
MaxAuthTries 3
MaxSessions 2
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
```
### 总结
正确配置 SSH 服务端对于保障服务器的安全至关重要。请根据您的具体需求和环境,结合上述指南进行配置。同时,定期审查和更新配置,以应对潜在的安全威胁。

View File

@@ -0,0 +1,77 @@
Termux更改终端密码:
```
passwd
```
重启SSH服务
```
pkill sshd
sshd
```
查看本机IP
```
ifconfig
```
生成SSH主机密钥
```
ssh-keygen -A
```
更新Termux包
```
pkg update && pkg upgrade
```
Termux退出chroot容器
```
exit
```
Termux安装软件
```
pkg install
```
查看终端用户名
```
whoami
```
妈的旧手机termux终端用户名
u0_a143
爸的旧手机termux终端用户名
u0_a135
Termux执行sh脚本
```
bash 脚本名.sh
```

View File

@@ -0,0 +1,37 @@
`termux-toast` 命令用于在 Android 设备上显示一个短暂的消息Toast。这个消息会在屏幕上弹出并在几秒钟后自动消失。它的基本用法如下
### 基本用法
```bash
termux-toast [选项] <消息文本>
```
### 常用选项
- `-s``--short`:使用短时间显示消息(默认选项)。
- `-l``--long`:使用较长时间显示消息。
- `-g``--gravity`:设置消息显示的位置。可选值为 `top``middle``bottom`(默认是 `bottom`)。
- `-b``--background`:设置背景颜色,使用十六进制颜色代码。
- `-c``--color`:设置文本颜色,使用十六进制颜色代码.
### 示例
1. **显示一个默认的短消息**
```bash
termux-toast "Hello, World!"
```
3. **在屏幕顶部显示消息**
```bash
termux-toast -g top "This message is at the top."
```
4. **设置背景和文本颜色**
```bash
termux-toast -b "#FF0000" -c "#FFFFFF" "Red background with white text."
```
这些命令可以在脚本中使用,以便在某些事件发生时通知用户。请注意,由于 Toast 本质上是短暂的通知类型,它们不适合显示需要用户交互或长时间关注的重要信息。

View File

@@ -0,0 +1,57 @@
当然!以下是一些使用 `termux-microphone-record` 命令的示例,展示了如何利用不同的参数来满足不同的录音需求:
1. **使用默认设置开始录音**
```bash
termux-microphone-record -d
```
这将使用默认设置开始录音,并保存到默认文件。
2. **录制到指定文件**
```bash
termux-microphone-record -f /sdcard/my_recording.wav
```
这会将录音保存到 `/sdcard/my_recording.wav` 文件中。
3. **限制录音时间为 30 秒**
```bash
termux-microphone-record -f /sdcard/my_recording.wav -l 30
```
这将录制 30 秒的音频,并保存到指定文件。
4. **使用特定编码器(例如 AAC录音**
```bash
termux-microphone-record -f /sdcard/my_recording.aac -e aac
```
这会使用 AAC 编码器进行录音。
5. **指定比特率为 128 kbps**
```bash
termux-microphone-record -f /sdcard/my_recording.wav -b 128
```
这将以 128 kbps 的比特率进行录音。
6. **使用特定采样率(例如 44100 Hz录音**
```bash
termux-microphone-record -f /sdcard/my_recording.wav -r 44100
```
这会以 44100 Hz 的采样率录音。
7. **使用双声道录音**
```bash
termux-microphone-record -f /sdcard/my_recording.wav -c 2
```
这会以双声道(立体声)进行录音。
8. **获取当前录音的信息**
```bash
termux-microphone-record -i
```
这将显示有关当前录音的详细信息。
9. **停止当前录音**
```bash
termux-microphone-record -q
```
这会停止当前正在进行的录音。
这些示例展示了如何使用 `termux-microphone-record` 的不同参数来调整录音的输出。你可以根据需要组合这些参数来实现更复杂的录音配置。

View File

@@ -0,0 +1,40 @@
Termux API 是一个提供设备功能访问的工具,允许在 Termux 环境中通过命令行接口访问 Android 设备的各种硬件和系统功能。以下是这些 Termux API 的简单介绍:
1. **termux-api-start/stop**:启动或停止 Termux API 服务。
2. **termux-audio-info**:获取设备的音频信息。
3. **termux-battery-status**:获取设备的电池状态信息。
4. **termux-brightness**:设置或获取屏幕亮度。
5. **termux-call-log**:访问设备的通话记录。
6. **termux-camera-info**:获取设备相机信息。
7. **termux-camera-photo**:使用设备相机拍照。
8. **termux-clipboard-get/set**:获取或设置设备的剪贴板内容。
9. **termux-contact-list**:获取设备的联系人列表。
10. **termux-dialog**:显示对话框。
11. **termux-download**:下载文件。
12. **termux-fingerprint**:使用指纹传感器进行身份验证。
13. **termux-infrared-frequencies/transmit**:获取红外频率信息或发送红外信号。
14. **termux-job-scheduler**:安排一个任务在特定时间运行。
15. **termux-keystore**:访问或管理密钥存储。
16. **termux-location**:获取设备的地理位置信息。
17. **termux-media-player**:播放媒体文件。
18. **termux-media-scan**:扫描媒体文件。
19. **termux-microphone-record**:录制音频。
20. **termux-nfc**:访问 NFC 功能。
21. **termux-notification/channel/list/remove**:管理通知,包括创建、列出和移除通知。
22. **termux-saf-create/dirs/ls/managedir/mkdir/read/rm/stat/write**:通过 Storage Access Framework (SAF) 访问和管理文件系统。
23. **termux-sensor**:访问设备的传感器数据。
24. **termux-share**:共享文件或文本。
25. **termux-sms-inbox/list/send**:访问和管理短信,包括查看收件箱和发送短信。
26. **termux-speech-to-text**:将语音转换为文本。
27. **termux-storage-get**:从设备存储中获取文件。
28. **termux-telephony-call/cellinfo/deviceinfo**:拨打电话或获取设备的电话信息。
29. **termux-toast**:显示一个短暂的消息。
30. **termux-torch**:控制设备的手电筒。
31. **termux-tts-engines/speak**:获取 TTS 引擎信息或合成语音。
32. **termux-usb**:访问 USB 设备。
33. **termux-vibrate**:控制设备的振动。
34. **termux-volume**:获取或设置音量。
35. **termux-wallpaper**:设置设备的壁纸。
36. **termux-wifi-connectioninfo/enable/scaninfo**:管理 Wi-Fi 连接,获取连接信息或扫描 Wi-Fi 网络。
这些 API 提供了丰富的功能,可以让用户通过脚本自动化许多任务或访问设备的硬件功能。

View File

@@ -0,0 +1,29 @@
`termux-torch` 命令用于控制 Android 设备的手电筒(通常是相机闪光灯)。可以通过此命令打开或关闭手电筒。它的基本用法如下:
### 基本用法
```bash
termux-torch <状态>
```
### 参数
- `<状态>`:指定手电筒的状态,可以是 `on``off`
- `on`:打开手电筒。
- `off`:关闭手电筒。
### 示例
1. **打开手电筒**
```bash
termux-torch on
```
2. **关闭手电筒**
```bash
termux-torch off
```
这些命令可以在脚本中使用,以便在特定情况下自动打开或关闭手电筒,比如在黑暗环境中需要光源时。使用这些命令时,请确保授予 Termux 应用程序相应的权限,以便访问设备的相机闪光灯功能。

View File

@@ -0,0 +1,126 @@
以下是 `shell2http` 项目的详细中文总结:
---
### **项目概述**
`shell2http` 是一个用 Go 编写的轻量级 HTTP 服务器,核心功能是通过 HTTP 请求触发执行预定义的 Shell 命令,并将结果返回给客户端。它适用于开发调试、原型设计、远程控制等场景,支持快速将 Shell 脚本暴露为 HTTP 接口。
---
### **核心功能**
1. **简单易用**
- 通过命令行参数直接绑定 URL 路径与 Shell 命令,例如:
```shell
shell2http /date "date" /ps "ps aux"
```
- 支持 GET、POST 等多种 HTTP 方法(通过 `METHOD:/path` 语法指定)。
2. **请求处理模式**
- **普通模式**直接执行命令返回标准输出stdout
- **表单模式(`-form`**:解析查询参数和上传文件,生成环境变量供脚本使用:
- `$v_参数名`:来自 URL 查询参数的值(如 `?id=123` → `$v_id`)。
- `$filepath_字段名`:上传文件的临时路径。
- `$filename_字段名`:上传文件的原始文件名。
- **CGI 模式(`-cgi`**:模拟 CGI 环境,设置 HTTP 请求相关环境变量,处理请求体数据。
3. **安全与认证**
- **Basic 认证**:通过 `-basic-auth` 设置用户名密码,支持多用户。
- **SSL/TLS**:使用 `-cert` 和 `-key` 选项启动 HTTPS 服务器。
- **表单字段过滤**:通过 `-form-check` 限制参数名格式(如仅允许数字)。
4. **高级特性**
- **缓存**`-cache=N` 缓存命令输出 N 秒。
- **超时控制**`-timeout` 设置命令执行超时时间。
- **错误处理**`-500` 在命令非零退出时返回 500 错误;`-show-errors` 显示错误输出。
- **环境变量**:支持导出特定或全部环境变量(`-export-vars`/`-export-all-vars`)。
5. **日志与调试**
- 自定义日志文件(`-log`)、禁用时间戳(`-no-log-timestamp`)。
---
### **安装方式**
1. **MacOS**
```shell
brew install msoap/tools/shell2http
```
2. **Docker**
```shell
docker pull msoap/shell2http
docker run -p 8080:8080 msoap/shell2http /date "date"
```
- 使用 `--init` 防止僵尸进程。
3. **SnapLinux**
```shell
sudo snap install shell2http
```
- 注意沙箱环境可能导致路径问题。
4. **源码编译**
```shell
go install github.com/msoap/shell2http@latest
```
5. **预编译二进制**
从 [GitHub Releases](https://github.com/msoap/shell2http/releases) 下载(支持 Windows、Linux、MacOS、树莓派
---
### **使用示例**
1. **基础命令**
```shell
shell2http /top "top -l 1 | head -10" /date "date"
```
- 访问 `http://localhost:8080/date` 返回当前时间。
2. **环境变量**
```shell
shell2http -export-vars=GOPATH /gopath 'echo $GOPATH'
```
3. **表单处理**
```shell
shell2http -form /upload 'cp $filepath_file1 ./uploads/$filename_file1'
```
- 上传文件并保存到 `uploads` 目录。
4. **CGI 模式**
```shell
shell2http -cgi /cgi-script './script.sh'
```
- 脚本可读取请求头、请求体,设置响应头。
5. **HTTPS 服务器**
```shell
shell2http -cert=cert.pem -key=key.pem /date "date"
```
- 生成自签名证书:
```shell
go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -host localhost
```
---
### **应用场景**
- **系统监控**:暴露 `top`、`ps`、`df` 等命令的 HTTP 接口。
- **文件操作**:上传/下载文件、目录列表。
- **远程控制**:调节音量、控制媒体播放器(如 MacOS 的 Vox.app
- **API 模拟**:返回静态 JSON 或动态生成数据。
- **调试工具**:模拟慢速响应、记录请求日志。
---
### **相关工具**
- **shell2telegram**:将 Shell 命令绑定到 Telegram 机器人。
- **websocketd**:将 STDIN/STDOUT 程序转换为 WebSocket 服务。
- **devd**:本地开发的轻量级 HTTP 守护进程。
---
### **注意事项**
- **安全性**:避免在生产环境暴露敏感命令,使用防火墙和认证机制。
- **性能**:默认多线程处理请求,可通过 `-one-thread` 限制为单线程。
- **路径问题**Docker 或 Snap 环境需注意命令的可访问性。
通过灵活配置,`shell2http` 可快速搭建功能丰富的 HTTP 服务,适合开发者和运维人员简化工作流程。

View File

@@ -0,0 +1,115 @@
`shell2http` 的灵活性和轻量级特性让它能玩出许多有趣的花样!以下是一些创意使用案例,涵盖实用场景和趣味玩法:
---
### **一、智能家居 & 自动化**
1. **远程控制智能插座**
```bash
shell2http -basic-auth="admin:密码" POST:/灯控 'curl -X POST http://智能插座IP/开关 -d "state=on"'
```
- 用途:用手机浏览器一键开关灯,配合 Siri 快捷指令还能语音控制。
2. **树莓派摄像头拍照**
```bash
shell2http /拍照 'raspistill -o /tmp/snapshot.jpg && base64 /tmp/snapshot.jpg'
```
- 用途:远程拍照并返回 Base64 图片,可嵌入网页实时查看。
3. **温湿度监控**
```bash
shell2http /温湿度 'python3 读取传感器脚本.py'
```
- 用途:连接 DHT11/DHT22 传感器,通过 HTTP 返回 JSON 格式温湿度数据。
---
### **二、实用工具**
4. **临时文件共享**
```bash
shell2http -form /upload 'mv $filepath_file1 ./共享文件夹/$filename_file1 && echo 上传成功'
```
- 用途:在局域网内快速共享文件,上传后生成直链供他人下载。
5. **二维码生成器**
```bash
shell2http /qrcode "qrencode -t ANSIUTF8 '$v_text'"
```
- 用法:访问 `http://IP:8080/qrcode?text=HelloWorld`,终端直接显示二维码。
6. **DDNS 动态域名更新**
```bash
shell2http /update_ddns 'curl "https://ddns服务商API?ip=$(curl ifconfig.me)"'
```
- 用途:外网触发更新 DDNS 记录,解决家庭宽带 IP 变化问题。
---
### **三、趣味玩法**
7. **语音播报远程控制**
```bash
shell2http POST:/说话 'echo "$v_text" | espeak -v zh' # Linux 文本转语音
```
- 用途:用手机输入文字,让电脑念出来(比如捉弄同事)。
8. **在线抽奖/随机选择**
```bash
shell2http /抽奖 'shuf -n1 名单.txt'
```
- 用途:年会时打开网页点击按钮,大屏幕显示中奖者。
9. **生成梗图**
```bash
shell2http -form /生成表情包 'convert 模板.jpg -pointsize 30 -fill white -annotate +100+200 "$v_text" -'
```
- 用途:上传文字生成表情包,可集成到微信群机器人。
---
### **四、开发调试**
10. **模拟慢速 API**
```bash
shell2http /slow 'sleep ${v_time:-5} && echo 响应完成'
```
- 用途:测试前端 Loading 效果,访问 `/slow?time=10` 模拟 10 秒延迟。
11. **Mock 测试接口**
```bash
shell2http /api/user 'echo "{\"name\":\"张三\",\"age\":$(shuf -i 20-60 -n1)}"'
```
- 用途:快速生成随机 JSON 数据,替代未完成的真实接口。
12. **代理请求记录器**
```bash
shell2http -cgi /proxy 'echo "URL: $REQUEST_URI" >> log.txt; curl -s "$v_url"'
```
- 用途:记录所有经过的请求参数,调试第三方回调。
---
### **五、系统管理**
13. **服务器监控面板**
```bash
shell2http /cpu "top -bn1 | grep 'Cpu(s)'" /内存 "free -h" /磁盘 "df -h"
```
- 用途:手机浏览器快速查看服务器状态。
14. **远程重启服务**
```bash
shell2http -500 POST:/reboot_nginx 'systemctl restart nginx && echo 成功'
```
- 用途:内网安全重启服务(务必设置 Basic 认证!)。
15. **日志实时查看**
```bash
shell2http /tail_log 'tail -n20 /var/log/syslog'
```
- 用途:快速检查日志,无需 SSH 登录。
---
### **注意事项**
- **安全第一**:暴露在公网时务必启用 `-basic-auth` 和 HTTPS。
- **权限控制**:敏感操作(如重启服务)建议结合 IP 白名单。
- **临时用途**:适合原型设计或内部工具,不建议替代专业 API 网关。
这些案例只需简单修改即可运行,试试用 `shell2http` 把你身边的硬件和脚本变成「物联网设备」吧!

View File

@@ -0,0 +1,25 @@
pkg  是用于管理 apt 软件包的工具,用法为  pkg [--check-mirror] command [arguments]  --check-mirror  强制重新检查镜像可用性。其命令包括:
 autoclean 从 apt 缓存中删除所有过时软件包。
 clean 从 apt 缓存中删除所有软件包。
 files <packages> :显示指定软件包安装的所有文件。
 install <packages> :安装指定软件包。
 list-all 列出仓库中所有可用软件包。
 list-installed 列出已安装的软件包。
 reinstall <packages> :以最新版本重新安装指定已安装软件包。
 search <query> :按名称或描述等查询搜索软件包。
 show <packages> :显示软件包基本元数据,如依赖项。
 uninstall <packages> :卸载指定软件包,保留配置文件。
 upgrade 将所有已安装软件包升级到最新版本。
 update 从配置的仓库更新 apt 数据库。

View File

@@ -0,0 +1,512 @@
以下是针对 **BusyBox** 中每条命令的简要说明:
1. **[**:测试表达式的真假。
2. **[[**:增强版的条件测试命令。
3. **acpid**:管理系统的 ACPI 事件。
4. **adjtimex**:调整系统时间。
5. **ar**:创建、修改、提取归档文件。
6. **arch**:显示机器架构类型。
7. **arp**:显示或修改 ARP 缓存。
8. **arping**:向网络发送 ARP 请求。
9. **ascii**:显示 ASCII 表。
10. **ash**:一个轻量级的 shell。
11. **awk**:强大的文本处理工具。
12. **base32**:以 Base32 编码格式进行数据转换。
13. **base64**:以 Base64 编码格式进行数据转换。
14. **basename**:从路径中提取文件名。
15. **bbconfig**:配置 BusyBox。
16. **beep**:产生蜂鸣声。
17. **blkdiscard**:丢弃磁盘块。
18. **blkid**:显示块设备的标识。
19. **blockdev**:对块设备进行操作。
20. **brctl**:管理网桥接口。
21. **bunzip2**:解压 `.bz2` 格式文件。
22. **bzcat**:查看 `.bz2` 格式文件内容。
23. **bzip2**:压缩文件为 `.bz2` 格式。
24. **cal**:显示日历。
25. **cat**:连接文件并显示内容。
26. **chat**:发送控制字符以进行调制解调器通信。
27. **chattr**:改变文件的属性。
28. **chcon**:改变文件的 SELinux 上下文。
29. **chgrp**:改变文件的组。
30. **chmod**:改变文件的权限。
31. **chown**:改变文件的拥有者。
32. **chroot**:改变根目录。
33. **chrt**:操作进程的调度策略。
34. **chvt**:切换虚拟终端。
35. **cksum**:计算文件的校验和。
36. **clear**:清除终端屏幕。
37. **cmp**:比较两个文件。
38. **comm**:比较两个已排序文件的内容。
39. **conspy**:查看控制台的输出。
40. **cp**:复制文件。
41. **cpio**:创建、解压 cpio 格式归档。
42. **crc32**:计算 CRC32 校验和。
43. **crond**:周期性执行任务的守护进程。
44. **crontab**:编辑 cron 表。
45. **cttyhack**:改变控制终端。
46. **cut**:按列剪切文本。
47. **date**:显示或设置系统日期和时间。
48. **dc**:计算器程序。
49. **dd**:转换和复制文件。
50. **deallocvt**:释放虚拟终端。
51. **depmod**:生成内核模块依赖关系。
52. **devmem**:访问物理内存。
53. **df**:显示文件系统的磁盘空间使用情况。
54. **dhcprelay**:转发 DHCP 请求。
55. **diff**:比较文件的不同之处。
56. **dirname**:获取路径的目录部分。
57. **dmesg**:显示内核的消息缓冲区内容。
58. **dnsd**:轻量级 DNS 服务器。
59. **dnsdomainname**:显示域名。
60. **dos2unix**:转换 DOS 格式文本为 Unix 格式。
61. **du**:显示磁盘使用情况。
62. **dumpkmap**:转储键盘映射。
63. **dumpleases**:显示 DHCP 租约。
64. **echo**:显示一行文本。
65. **ed**:文本编辑器。
66. **egrep**:扩展正则表达式的 grep 命令。
67. **eject**:弹出光盘。
68. **env**:显示或设置环境变量。
69. **ether-wake**:发送 Wake-on-LAN 魔术包。
70. **expand**:将制表符转换为空格。
71. **expr**:计算表达式的值。
72. **factor**:因数分解。
73. **fakeidentd**:模拟 identd 服务。
74. **false**:返回失败状态。
75. **fatattr**:显示或修改 FAT 文件系统的属性。
76. **fbset**:设置帧缓冲设备的参数。
77. **fbsplash**:管理帧缓冲启动画面。
78. **fdflush**:刷新文件描述符。
79. **fdformat**:格式化软盘。
80. **fdisk**:管理磁盘分区。
81. **fgconsole**:切换到指定的虚拟终端。
82. **fgrep**:固定字符串搜索工具。
83. **find**:搜索文件。
84. **findfs**:查找文件系统。
85. **flash_eraseall**:擦除闪存设备。
86. **flash_lock**:锁定闪存设备。
87. **flash_unlock**:解锁闪存设备。
88. **flock**:对文件进行锁定。
89. **fold**:折叠文本行。
90. **free**:显示内存使用情况。
91. **freeramdisk**:释放 RAM 磁盘。
92. **fsck**:检查文件系统的完整性。
93. **fsck.minix**:检查 Minix 文件系统。
94. **fsfreeze**:冻结文件系统以进行备份。
95. **fstrim**:修剪未使用的磁盘空间。
96. **fsync**:强制磁盘同步。
97. **ftpd**:启动 FTP 服务器。
98. **ftpget**:下载文件通过 FTP。
99. **ftpput**:上传文件通过 FTP。
100. **fuser**:显示正在使用某个文件的进程。
101. **getenforce**:显示 SELinux 的状态。
102. **getopt**:解析命令行选项。
103. **grep**:搜索文本中的模式。
104. **groups**:显示用户所属的组。
105. **gunzip**:解压 `.gz` 文件。
106. **gzip**:压缩文件为 `.gz` 格式。
107. **hd**:显示硬盘的分区信息。
108. **hdparm**:设置硬盘参数。
109. **head**:显示文件的开头部分。
110. **hexdump**:以十六进制格式显示文件内容。
111. **hexedit**:编辑十六进制文件。
112. **hostname**:显示或设置主机名。
113. **httpd**:启动 HTTP 服务器。
114. **hush**:一个小型 shell。
115. **hwclock**:访问硬件时钟。
116. **id**:显示用户和组信息。
117. **ifconfig**:配置网络接口。
118. **ifdown**:关闭网络接口。
119. **ifenslave**:配置链路聚合。
120. **ifplugd**:监控网络接口的连接状态。
121. **ifup**:启用网络接口。
122. **inetd**:启动网络服务守护进程。
123. **inotifyd**:启动文件系统事件监控。
124. **insmod**:加载内核模块。
125. **install**:复制文件并设置权限。
126. **ionice**:设置进程的 I/O 优先级。
127. **iostat**:显示 CPU 和 I/O 统计信息。
128. **ip**:配置网络接口和路由。
129. **ipaddr**:显示或设置 IP 地址。
130. **ipcalc**:计算和显示 IP 地址信息。
131. **ipcrm**:删除共享内存、消息队列、信号量。
132. **ipcs**:显示进程间通信的状态。
133. **iplink**:管理网络接口的状态。
134. **ipneigh**:显示邻居表。
135. **iproute**:管理路由表。
136. **iprule**:显示或管理路由规则。
137. **iptunnel**:配置 IP 隧道。
138. **kbd_mode**:设置键盘模式。
139. **kill**:终止进程。
140. **killall**:终止指定名称的所有进程。
141. **killall5**:终止所有进程。
142. **klogd**:内核日志守护进程。
143. **less**:分页显示文件内容。
144. **link**:创建硬链接。
145. **ln**:创建硬链接或符号链接。
146. **loadfont**:加载字体。
147. **loadkmap**:加载键盘映射。
148. **logread**:显示日志文件内容。
149. **losetup**:管理环回设备。
150. **ls**:列出目录内容。
继续接着之前的命令列表进行总结:
151. **lsattr**:显示文件的属性。
152. **lsmod**:显示加载的内核模块。
153. **lsof**:列出打开的文件。
154. **lspci**:显示所有 PCI 设备。
155. **lsscsi**:显示 SCSI 设备信息。
156. **lsusb**:列出 USB 设备。
157. **lzcat**:解压 `.lz` 文件并显示内容。
158. **lzma**:压缩文件为 `.lzma` 格式。
159. **lzop**:快速的文件压缩工具。
160. **lzopcat**:查看 `.lzo` 格式文件内容。
161. **makedevs**:创建设备节点。
162. **makemime**:生成 MIME 类型文件。
163. **man**:查看手册页(如果有)。
164. **md5sum**:计算文件的 MD5 校验和。
165. **mesg**:控制终端消息的接收。
166. **microcom**:串行通信工具。
167. **mim**:提取文件的 MIME 类型。
168. **mkdir**:创建目录。
169. **mkdosfs**:创建 FAT 文件系统。
170. **mke2fs**:创建 ext2 文件系统。
171. **mkfifo**:创建命名管道。
172. **mkfs.ext2**:创建 ext2 文件系统。
173. **mkfs.minix**:创建 Minix 文件系统。
174. **mkfs.reiser**:创建 Reiser 文件系统。
175. **mkfs.vfat**:创建 VFAT 文件系统。
176. **mknod**:创建块设备或字符设备文件。
177. **mkswap**:创建交换分区。
178. **mktemp**:创建临时文件。
179. **modinfo**:显示内核模块的信息。
180. **modprobe**:加载或卸载内核模块。
181. **more**:分页显示文件内容。
182. **mount**:挂载文件系统。
183. **mountpoint**:检查是否为挂载点。
184. **mpstat**:显示 CPU 使用统计信息。
185. **mv**:移动或重命名文件。
186. **nameif**:根据 MAC 地址设置网络接口名称。
187. **nanddump**:转储 NAND 闪存的内容。
188. **nandwrite**:将数据写入 NAND 闪存。
189. **nbd-client**:连接到网络块设备。
190. **nc**Netcat 工具,用于读写网络连接。
191. **netstat**:显示网络连接状态。
192. **nice**:调整进程的优先级。
193. **nl**:显示带有行号的文件内容。
194. **nmeter**:网络带宽监控工具。
195. **nohup**:在后台运行命令并忽略挂起信号。
196. **nologin**:禁止用户登录。
197. **nsenter**:进入其他命名空间。
198. **nslookup**:查询 DNS 信息。
199. **nuke**:终止所有与特定进程相关的网络连接。
200. **od**:以不同格式显示文件内容(如十六进制)。
201. **openvt**:在指定的虚拟终端上运行命令。
202. **partprobe**:通知操作系统重新读取分区表。
203. **paste**:合并多个文件按列显示。
204. **patch**:应用补丁文件。
205. **pgrep**:查找匹配指定模式的进程。
206. **pidof**:查找指定进程的 PID。
207. **ping**:向网络主机发送 ICMP 请求。
208. **ping6**:向网络主机发送 ICMPv6 请求。
209. **pipe_progress**:显示管道中的数据传输进度。
210. **pivot_root**:更改文件系统根目录。
211. **pkill**:根据进程名终止进程。
212. **pmap**:显示进程的内存映射。
213. **popmaildir**:从邮件目录中获取邮件。
214. **poweroff**:关闭计算机。
215. **powertop**:用于优化电源管理的工具。
216. **printenv**:显示所有环境变量。
217. **printf**:格式化并输出文本。
218. **ps**:显示当前进程信息。
219. **pscan**:扫描当前进程的状态。
220. **pstree**:以树形结构显示进程。
221. **pwd**:显示当前工作目录。
222. **pwdx**:显示指定进程的工作目录。
223. **raidautorun**:自动启动 RAID 配置。
224. **rdate**:同步时间。
225. **rdev**:显示或设置设备的特殊属性。
226. **readlink**:显示符号链接的目标。
227. **readprofile**:读取并显示应用的性能配置。
228. **realpath**:返回文件的绝对路径。
229. **reboot**:重启系统。
230. **reformime**:转换 MIME 格式。
231. **renice**:改变进程的优先级。
232. **reset**:重置终端。
233. **resize**:调整终端大小。
234. **resume**:恢复挂起的进程。
235. **rev**:反转每行文本。
236. **rfkill**:管理无线设备的开关。
237. **rm**:删除文件。
238. **rmdir**:删除空目录。
239. **rmmod**:卸载内核模块。
240. **route**:显示或修改路由表。
241. **rtcwake**:设置系统的 RTC 来定时唤醒。
242. **run-init**:运行初始化程序。
243. **run-parts**:按顺序运行目录中的脚本。
244. **runcon**:设置进程的 SELinux 上下文。
245. **rx**:接收串口数据。
246. **script**:记录终端会话。
247. **scriptreplay**:重放记录的终端会话。
248. **sed**:流编辑器,用于处理文本数据。
249. **seedrng**:种子随机数生成器。
250. **selinuxenabled**:检查 SELinux 是否启用。
251. **sendmail**:发送电子邮件。
252. **seq**:生成一个数字序列。
253. **sestatus**:显示 SELinux 状态。
254. **setconsole**:设置控制台的终端类型。
255. **setenforce**:启用或禁用 SELinux 强制模式。
256. **setfattr**:设置文件的扩展属性。
257. **setfont**:设置终端的字体。
258. **setkeycodes**:设置键盘扫描码到键值的映射。
259. **setlogcons**:设置日志控制台。
260. **setpriv**:设置进程的特权级别。
261. **setserial**:设置串口设备的参数。
262. **setsid**:创建新的会话并运行命令。
263. **setuidgid**:设置进程的用户和组标识。
264. **sh**:启动一个新的 shell。
265. **sha1sum**:计算文件的 SHA-1 校验和。
266. **sha256sum**:计算文件的 SHA-256 校验和。
267. **sha3sum**:计算文件的 SHA-3 校验和。
268. **sha512sum**:计算文件的 SHA-512 校验和。
269. **showkey**:显示键盘输入的键值。
270. **shred**:擦除文件,防止数据恢复。
271. **shuf**:随机排列输入行。
272. **slattach**:设置串行连接。
273. **sleep**:暂停一段时间。
274. **smemcap**:设置进程内存使用限制。
275. **sort**:按行排序文本文件。
276. **split**:将文件分割成多个小文件。
277. **ssl_client**:通过 SSL 连接远程主机。
278. **start-stop-daemon**:启动或停止后台守护进程。
279. **stat**:显示文件或文件系统的状态。
280. **strings**:显示文件中的可打印字符串。
281. **stty**:设置终端行属性。
282. **sum**:计算文件的校验和。
283. **svc**:启动、停止或重启服务。
284. **svok**:检查服务的状态。
285. **swapoff**:禁用交换空间。
286. **swapon**:启用交换空间。
287. **switch_root**:切换到新的根文件系统。
288. **sync**:同步文件系统。
289. **sysctl**:显示或设置内核参数。
290. **syslogd**:启动系统日志守护进程。
291. **tac**:反向显示文件内容。
292. **tail**:显示文件的最后部分。
293. **tar**:创建和解压 tar 归档。
294. **tc**:配置网络流量控制。
295. **tcpsvd**TCP 服务守护进程。
296. **tee**:将输入内容输出到多个文件。
297. **telnet**:远程登录到另一个计算机。
298. **telnetd**:启动 Telnet 服务器。
299. **test**:检查条件的真假。
300. **tftp**:简易的文件传输协议客户端。
301. **tftpd**TFTP 服务器。
302. **time**:测量命令执行时间。
303. **timeout**:设置命令的超时时间。
304. **top**:显示系统进程信息。
305. **touch**:创建空文件或更新文件的时间戳。
306. **tr**:替换或删除字符。
307. **traceroute**:追踪数据包在网络中的路由。
308. **traceroute6**:追踪 IPv6 数据包的路由。
309. **tree**:以树形结构显示目录内容。
310. **true**:始终返回成功状态。
311. **truncate**:截断文件到指定长度。
312. **ts**:为每行添加时间戳。
313. **tsort**:排序文件中的时间戳。
314. **tty**:显示终端设备名称。
315. **ttysize**:显示终端的大小。
316. **tunctl**:管理 TUN/TAP 网络设备。
317. **tune2fs**:调整 ext2/ext3/ext4 文件系统的参数。
318. **ubiattach**:附加 UBI 设备。
319. **ubidetach**:分离 UBI 设备。
320. **ubimkvol**:创建 UBI 卷。
321. **ubirename**:重命名 UBI 卷。
322. **ubirmvol**:删除 UBI 卷。
323. **ubirsvol**:恢复 UBI 卷。
324. **ubiupdatevol**:更新 UBI 卷的数据。
325. **udhcpc**DHCP 客户端,用于获取网络配置信息。
326. **udhcpc6**DHCPv6 客户端。
327. **udhcpd**DHCP 服务器。
328. **udpsvd**UDP 服务守护进程。
329. **uevent**:显示系统的 udev 事件。
330. **umount**:卸载文件系统。
331. **uname**:显示系统信息。
332. **uncompress**:解压 `.Z` 格式文件。
333. **unexpand**:将空格转换为制表符。
334. **uniq**:删除重复的行。
335. **unix2dos**:转换 Unix 格式文本为 DOS 格式。
336. **unlink**:删除文件。
337. **unlzma**:解压 `.lzma` 格式文件。
338. **unlzop**:解压 `.lzo` 格式文件。
339. **unshare**:在新命名空间中运行命令。
340. **unxz**:解压 `.xz` 格式文件。
341. **unzip**:解压 `.zip` 文件。
342. **uptime**:显示系统的运行时间和负载。
343. **usleep**:暂停指定的微秒数。
344. **uudecode**:解码 `.uu` 格式文件。
345. **uuencode**:编码文件为 `.uu` 格式。
346. **vconfig**:管理 VLAN 网络接口。
347. **vi**:文本编辑器。
348. **volname**:显示文件系统的卷标。
349. **watch**:定期执行并显示命令输出。
350. **watchdog**:监控系统状态并在需要时重启。
351. **wc**:计算文件的行数、字数和字符数。
352. **wget**:下载文件。
353. **which**:显示可执行文件的路径。
354. **whoami**:显示当前用户的用户名。
355. **whois**:查询域名注册信息。
356. **xargs**:将标准输入转换为命令行参数。
357. **xxd**:创建十六进制转储。
358. **xz**:压缩文件为 `.xz` 格式。
359. **xzcat**:查看 `.xz` 格式文件内容。
360. **yes**:输出指定字符串,通常用来测试管道。
361. **zcat**:查看 `.gz` 格式文件内容。
362. **zcip**:配置 ZCIP 地址。
这些命令覆盖了文件操作、系统管理、网络配置、进程管理等多个方面,是 BusyBox 在资源受限环境中为用户提供常用功能的集合。

View File

@@ -0,0 +1,36 @@
### 一句命令启动busybox的简单http服务器
```shell
busybox httpd -f -p 端口 -h 目录
```
### 使用 `busybox netstat` 查看端口占用情况
- `-t`:显示 TCP 连接
- `-u`:显示 UDP 连接
- `-l`:只显示正在监听的端口
- `-n`:显示数字形式的地址和端口号,而不进行域名解析
```shell
busybox netstat -tuln
```
### 启动busybox的ftp服务器
```shell
busybox ftp -d 目录 -p 端口
busybox ftp -d 目录 #默认占用21端口
sudo mkdir -p 目录
sudo chmod 755 目录
```
### 启动 `busybox` 的 Telnet 服务器不安全的ssh
```
busybox telnetd -l /bin/login
busybox telnetd -p 2323 -l /bin/login
```

View File

@@ -0,0 +1,82 @@
`crontab` 是一个用于在 Unix 和类 Unix 系统(如 Linux中设置定时任务的工具。它允许用户根据指定的时间间隔安排脚本或命令的执行。以下是关于 `crontab` 使用方法的详细介绍和示例。
### 基本概念
`crontab` 的配置文件由一系列行组成,每行代表一个定时任务,其基本格式如下:
```plaintext
* * * * * command
```
这五个 `*` 分别代表分钟0 - 59、小时0 - 23、日期1 - 31、月份1 - 12和星期0 - 7其中 0 和 7 都代表星期日),`command` 是要执行的命令或脚本。
### 使用步骤
#### 1. 编辑 `crontab` 文件
可以使用以下命令编辑当前用户的 `crontab` 文件:
```bash
crontab -e
```
首次使用时,系统会提示选择一个文本编辑器,选择你熟悉的编辑器(如 `nano``vim`)即可。
#### 2. 添加定时任务
在打开的 `crontab` 文件中添加定时任务,下面是一些具体的示例:
##### 示例 1每分钟执行一次命令
```plaintext
* * * * * /usr/bin/echo "This is a test" >> /tmp/test.log
```
这个任务会每分钟执行一次,将 `"This is a test"` 追加到 `/tmp/test.log` 文件中。
##### 示例 2每小时的第 30 分钟执行一次命令
```plaintext
30 * * * * /usr/bin/backup_script.sh
```
这个任务会在每小时的第 30 分钟执行 `/usr/bin/backup_script.sh` 脚本。
##### 示例 3每天凌晨 2 点执行一次命令
```plaintext
0 2 * * * /usr/bin/daily_cleanup.sh
```
这个任务会在每天凌晨 2 点执行 `/usr/bin/daily_cleanup.sh` 脚本。
##### 示例 4每月 1 号的 3 点 15 分执行一次命令
```plaintext
15 3 1 * * /usr/bin/monthly_report.sh
```
这个任务会在每月 1 号的 3 点 15 分执行 `/usr/bin/monthly_report.sh` 脚本。
##### 示例 5每周日的 18 点执行一次命令
```plaintext
0 18 * * 0 /usr/bin/weekly_backup.sh
```
这个任务会在每周日的 18 点执行 `/usr/bin/weekly_backup.sh` 脚本。
##### 示例 6指定时间范围执行命令
```plaintext
0 9-17 * * 1-5 /usr/bin/workday_check.sh
```
这个任务会在周一至周五的 9 点到 17 点之间,每小时的整点执行 `/usr/bin/workday_check.sh` 脚本。
##### 示例 7每隔一段时间执行命令
```plaintext
*/15 * * * * /usr/bin/check_status.sh
```
这个任务会每隔 15 分钟执行一次 `/usr/bin/check_status.sh` 脚本。
#### 3. 保存并退出
编辑完成后,保存并退出文本编辑器。如果使用 `nano` 编辑器,按 `Ctrl + X`,然后按 `Y` 确认保存,最后按 `Enter` 键退出。如果使用 `vim` 编辑器,按 `Esc` 键,输入 `:wq` 并按 `Enter` 键保存并退出。
#### 4. 查看 `crontab` 文件内容
可以使用以下命令查看当前用户的 `crontab` 文件内容:
```bash
crontab -l
```
#### 5. 删除 `crontab` 文件
如果需要删除当前用户的所有定时任务,可以使用以下命令:
```bash
crontab -r
```
### 注意事项
- **环境变量**`crontab` 任务在执行时使用的环境变量可能与用户登录时不同。如果脚本依赖特定的环境变量,需要在脚本中明确设置。
- **日志记录**:建议将定时任务的输出重定向到日志文件,以便后续排查问题。
- **权限问题**:确保执行的命令或脚本具有足够的权限。

View File

@@ -0,0 +1,120 @@
以下从技术定义、核心原理、特点、区别及相互关系等方面,详细解析 **LXC、Docker、chroot、虚拟机、QEMU、K8s** 的概念及关联:
### **一、核心技术定义与原理**
#### 1. **chroot**
- **本质**Linux 系统调用,用于修改进程的“根目录”(`/`),创建一个隔离的文件系统视图。
- **原理**:通过 `chroot` 命令,指定一个新的根目录,进程只能访问该目录下的文件(类似“伪根”),但不隔离 CPU、内存、网络等资源。
- **特点**
- 最轻量的隔离,仅文件系统隔离,无资源限制。
- 进程仍共享宿主机内核、进程空间、网络栈等。
- 非完整容器,常用于调试、环境隔离(如构建跨平台程序)。
#### 2. **LXCLinux Containers**
- **本质**:基于 Linux 内核特性(`namespace` 命名空间 + `cgroups` 资源控制)的操作系统级容器。
- **原理**
- `namespace`:隔离进程、网络、文件系统、用户等资源(如 `PID namespace` 使容器内进程号独立)。
- `cgroups`:限制容器的 CPU、内存、磁盘 I/O 资源使用。
- **特点**
- 共享宿主机内核,启动快(秒级),资源占用低。
- 提供接近虚拟机的隔离性,但轻量高效。
- 需要手动配置,侧重系统级隔离(如运行完整的 Linux 发行版)。
#### 3. **Docker**
- **本质**:基于容器技术的应用打包与部署平台,核心是容器运行时(`runc`,基于 Open Container Initiative 标准)。
- **原理**
- 继承 LXC 的 `namespace``cgroups`,但更上层,封装了镜像(`Image`)、容器(`Container`)、仓库(`Registry`)流程。
- 通过镜像分层技术UnionFS实现快速打包通过 `Docker Engine` 管理容器生命周期。
- **特点**
- 聚焦“应用容器化”,强调“一次构建,到处运行”。
- 比 LXC 更易用(标准化 API、自动化部署适合微服务、CI/CD。
- 镜像生态丰富Docker Hub但隔离性略弱于 LXC共享内核依赖宿主机内核版本
#### 4. **虚拟机Virtual Machine, VM**
- **本质**:通过虚拟化技术模拟完整硬件环境,运行独立操作系统。
- **分类与原理**
- **全虚拟化**(如 VMware WorkstationHypervisor 完全模拟硬件Guest OS 无需修改。
- **半虚拟化**(如 XenGuest OS 知道自己运行在虚拟机中,通过 Hypercall 与 Hypervisor 交互。
- **硬件辅助虚拟化**(如 Intel VT-xCPU 直接支持虚拟化,提升性能。
- **特点**
- 隔离性最强(独立内核、硬件资源),支持不同操作系统(如 Windows 跑在 Linux 宿主机上)。
- 资源开销大(需模拟硬件,启动慢,内存/CPU 占用高)。
- 适合需要完整 OS 环境的场景(如测试不同系统、遗留应用迁移)。
#### 5. **QEMU**
- **本质**开源的通用模拟器和虚拟机监视器Virtual Machine Monitor, VMM
- **原理**
- 单独使用时,通过软件模拟目标硬件(如在 x86 上运行 ARM 程序),性能较低。
-**KVM**Kernel-based Virtual MachineLinux 内核模块)结合时,利用硬件虚拟化技术(如 VT-x成为高效的虚拟机引擎QEMU-KVM
- **特点**
- 跨平台兼容性强支持多种架构x86、ARM、RISC-V 等)。
- 是虚拟机技术的具体实现之一,常作为 KVM 的用户空间工具。
#### 6. **K8sKubernetes**
- **本质**:容器编排平台,用于自动化部署、扩展和管理容器化应用。
- **核心功能**
- 调度容器到节点Node支持负载均衡、服务发现。
- 处理容器的生命周期(重启、扩缩容、滚动更新)。
- 提供资源配额、健康检查、故障恢复等机制。
- **特点**
- 不绑定特定容器运行时(支持 Docker、containerd、runc 等)。
- 解决“大规模容器集群管理”问题,适合微服务架构、云原生应用。
### **二、核心区别对比**
| **维度** | **chroot** | **LXC/Docker容器** | **虚拟机(含 QEMU-KVM** | **K8s** |
|-------------------|------------------|------------------------|---------------------------|------------------------|
| **隔离级别** | 文件系统隔离 | 操作系统级隔离(内核共享) | 硬件级隔离(独立内核) | 编排管理层(不涉及底层隔离) |
| **资源共享** | 完全共享 | 共享宿主机内核 | 独立内核,硬件资源模拟 | 管理多个容器/节点的资源 |
| **启动时间** | 瞬间 | 秒级 | 分钟级(需启动 Guest OS | 不涉及启动,管理已有容器 |
| **资源开销** | 极低 | 低(仅用户空间隔离) | 高(硬件模拟 + 完整 OS | 额外控制平面开销 |
| **支持的 OS** | 同宿主机内核 | 同宿主机内核 | 任意 OS如 Windows/Linux | 不限制,管理容器化应用 |
| **核心目标** | 文件系统隔离 | 轻量应用隔离/部署 | 完整 OS 环境模拟 | 容器集群管理 |
| **典型用途** | 环境调试 | 应用打包、轻量部署 | 多系统测试、遗留应用兼容 | 大规模容器调度、微服务 |
### **三、相互关系**
#### 1. **技术栈分层**
```
应用层K8s
├─ 容器运行时Docker/LXC/containerd
│ ├─ 内核特性namespace/cgroups
│ └─ chroot基础文件系统隔离
└─ 虚拟化层(虚拟机/QEMU-KVM
└─ 硬件CPU/内存/存储,支持 VT-x 等虚拟化技术)
```
#### 2. **具体关联**
- **chroot 与容器**
chroot 是容器实现文件系统隔离的基础(如 Docker 镜像的根文件系统通过 chroot 挂载),但容器在此之上增加了 `namespace``cgroups` 实现完整隔离。
- **LXC 与 Docker**
Docker 早期基于 LXC 开发,后转向自有运行时(`libcontainer`,现 `runc`),二者同属容器技术,但定位不同:
- LXC 偏向“系统级容器”(运行完整 Linux 发行版,如 Ubuntu 容器);
- Docker 偏向“应用级容器”(打包单个应用及其依赖,如 Node.js 服务)。
- **虚拟机与 QEMU**
QEMU 是虚拟机的一种实现方式,单独使用时模拟硬件(慢),与 KVM 结合时利用硬件虚拟化(高效),属于虚拟机技术的底层引擎之一(类似 VirtualBox、VMware 的技术定位)。
- **K8s 与容器/虚拟机**
- K8s 主要管理容器(如 Docker 容器但也可通过虚拟机节点Node部署每个 Node 可以是物理机或虚拟机)。
- 容器运行在宿主机物理机或虚拟机K8s 不关心底层是物理机还是虚拟机,只调度容器到节点。
### **四、应用场景总结**
- **chroot**:简单文件系统隔离(如构建跨版本软件、修复系统)。
- **Docker/LXC**轻量应用部署微服务、Web 服务),追求资源效率和快速启动。
- **虚拟机/QEMU-KVM**:需要完整 OS 隔离(如运行 Windows 应用、多租户隔离、硬件兼容性测试)。
- **K8s**:大规模容器集群管理(微服务架构、弹性扩缩容、高可用部署)。
### **五、核心总结**
- **隔离性**:虚拟机(强隔离,独立内核)> 容器共享内核OS 级隔离)> chroot仅文件系统
- **效率**chroot/容器(高效)> 虚拟机(低效,因硬件模拟)。
- **关系**
- 容器技术LXC/Docker基于内核特性namespace/cgroupschroot 是其文件系统隔离的基础。
- 虚拟机(如 QEMU-KVM通过硬件虚拟化实现强隔离与容器形成互补容器适合同一 OS 下的应用隔离,虚拟机适合跨 OS 隔离)。
- K8s 是容器的“上层管理工具”,不依赖特定容器运行时,聚焦集群调度与自动化。
理解这些技术的核心是明确其“隔离目标”和“资源模型”容器追求轻量高效虚拟机追求强隔离K8s 解决规模化管理问题,而 chroot 是最基础的工具。根据需求选择合适的技术(如部署单个应用用 Docker多 OS 环境用虚拟机,大规模集群用 K8s 编排容器)。

View File

@@ -0,0 +1,16 @@
QQ机器人napcat
docker run -d \
-e NAPCAT_GID=$(id -g) \
-e NAPCAT_UID=$(id -u) \
-p 3000:3000 \
-p 3001:3001 \
-p 3002:6099 \
--name napcat \
--restart=always \
mlikiowa/napcat-docker:latest
微信机器人gewe
docker run -itd -v /shumengya/bin/wechatbot:/root/temp -p 2531:2531 -p 2532:2532 --privileged=true --name=gewe gewe /usr/sbin/init

View File

@@ -0,0 +1,60 @@
### 使用 yum 安装 systemdCentOS/RHEL
```
yum install systemd
```
### 使用 apt 安装 systemdDebian/Ubuntu
```
apt install systemd
sudo vim /etc/systemd/system/frps.service
```
### 服务名称,可自定义
```
Description = frp server
After = network.target syslog.target
Wants = network.target
[Service]
Type = simple
```
### 启动frps的命令需修改为您的frps的安装路径
```
ExecStart = /path/to/frps -c /path/to/frps.toml
[Install]
WantedBy = multi-user.target
```
### 启动frp
```
sudo systemctl start frps
```
### 停止frp
```
sudo systemctl stop frps
```
### 重启frp
```
sudo systemctl restart frps
```
### 查看frp状态
```
sudo systemctl status frps
sudo systemctl enable frps
```

View File

@@ -0,0 +1,39 @@
1. **markdown-it**:
- **简介**: 一个功能强大且可扩展的 Markdown 解析器,支持插件和自定义规则。
- **安装**: 可以通过 npm 安装:`npm install markdown-it`
- **使用示例**:
```javascript
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
const result = md.render('# 你好Markdown!');
console.log(result);
```
2. **marked**:
- **简介**: 一个快速、轻量级的 Markdown 解析器,支持自定义渲染器。
- **安装**: 使用 npm 安装:`npm install marked`
- **使用示例**:
```javascript
const marked = require('marked');
const result = marked('# 你好Markdown!');
console.log(result);
```
3. **remark**:
- **简介**: 一个强大的 Markdown 处理库,支持解析、检查和转换 Markdown 文档。
- **安装**: 通过 npm 安装:`npm install remark remark-html`
- **使用示例**:
```javascript
const remark = require('remark');
const html = require('remark-html');
remark()
.use(html)
.process('# 你好Markdown!', function (err, file) {
if (err) throw err;
console.log(String(file));
});
```
这些库各有优点,选择哪个库取决于您的具体需求,比如性能、可扩展性、支持的特性等。如果你需要简单的解析和渲染,`marked` 和 `markdown-it` 都是不错的选择;如果需要更多的可扩展性和插件支持,`remark` 可能更适合。

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