树莓派微型web服务器——阶段设计报告

1. 需求分析

本项目是用树莓派制作一个轻量级的web服务器,提供一个公网地址供所有用户(也可限定账号登录)来进行下载图片、文件、与gpt3.5进行聊天、与midjourney进行绘画交互。简单来说,制作一个集图片、文件、聊天、绘画四个功能模块的web,用树莓派作为其web服务器,利用apache2生成内网网址,再使用ngrok进行内网穿透,使得其在公网中也可访问。

1.1 功能需求

1.1.1 访问需求
  • 普通用户访问需求:

普通用户可能有时候需要某一个特定的地方下载文件,比如学生从mystu上下载学习资料或者到ftp里面下载一些共享的文件,亦或者从邮箱上下载某些文件。在下载的时候会遇到种种问题,其中比较特别的就是离开了该局域网后就不能下载某个网站下的图片或者文件了,因此树莓派可以用作公网的微服务器来用于web的搭建、文件的传输、图片的下载…等等。

  • web开发者访问需求:

对于web开发者,假如做出了一个vue\ react 或者其他简单的html的静态页面,想让公网的全部人都看到并且能够输入网站进入该页面,而不是只有localhost或者局域网内的用户能看到,那么树莓派+apache2+ngrok+内网穿透+vue build就能实现这一点。

1.1.2 自定义域名需求

其实对于静态网站的部署有很多办法,其中免费且稳定的方法就有GitHub Page,下图就是一个使用Github Page部署一个用Slidev构建的线上ppt的例子,使用https://hiddensharp429.github.io/slidev/1就可以访问这个部署在Github上的项目了。html项目、vue打包后的项目、react打包后的项目等等都可以直接部署在Github上面,但是这个<authorname>.github.io这个二级域名你是不可以修改的,只有你购买的域名才有修改这个二级域名的权利。

由此就凸显了树莓派+ngrok的威力了,使用ngrok部署的项目可以自定义二级域名例如可以使用https://hiddensharppi.ap.ngrok.io \ https://hiddensharppi.in.ngrok.io/\ https://hiddensharppi.ngrok.com/等等域名,并且hiddensharppi这个字段是可以任意编辑的(例如我改成**https://ilovestudy.ap.ngrok.io/**完全可以)。

image-20230628224103247

image1 Github Page静态页面部署

image-20230628224141497

image2 Github Page部署Slidev项目
1.1.3 下载公共文件需求

假如有多个人需要相互之间传输下载文件,但是又不想使用微信等通讯工具来满足其需求,这时候树莓派就可以充当一个小型的文件服务器了,如果这几个人在同一个局域网内进行通讯就可以直接通过访问树莓派内存里面的文件夹share来进行文件的传输,该项目的注重的是在公网内用户如何达到这样的一个目的:“管理员上传相关的文件,来自不同局域网的用户通过输入网址来访问树莓派服务器,点击相关文件的url链接来下载他们所需要的文件”。

1.1.4 用户体验需求

用户使用树莓派的web服务器主要要求服务器具有如下特征:

  1. 域名友好的
  2. 服务稳定的
  3. 延迟较低的

对于第一点域名友好的,树莓派web服务器可以使用ngrok来进行二级域名和最大程度的域名自定义,由此可以实现第一点;

对于第二点服务稳定的,树莓派相对于电脑作为web服务器,树莓派本身机身小,不需要关机,连接简单,操作易上手等等特性使得其可以称得上是微型web服务器的最优选;

对于第三点延迟较低的,树莓派web服务器原本是用apache2进行局域网内的网址部署,但是利用了ngrok进行了内网穿透,使得本来只能局域网内访问的网址变成了在联网的情况上均可以访问的网址。

1.2 技术需求

1.2.1 操作系统指令

树莓派通常使用Raspbian作为默认操作系统,它是基于Linux的。同时也可以选择使用其他的操作系统,例如Ubuntu等,若想要完成这个项目,要求对Linux系统操作命令拥有一定的熟练度。

1.2.2 技术栈

本项目主要的涉及的技术栈有RaspbianApacheNginxhttp\tcp\ip协议ngroksshvue.jsnode.jshtmlcss等,由于该项目的树莓派使用的内存是16G的,故没有涉及到后端开发,因此之前所搭建的个人Chatgpt网站和Midjourney网站就没有上传至树莓派服务器。

1.2.3 内网穿透

本项目主要选择方向是想使用的ngrok来进行树莓派的内网穿透,主要使用过内网穿透有:国内之前比较好用的ngrok.ttun\ frp \ ngrok.ciqiuwl \ ngrok等,该项目的最后选择的是ngrok

1.3 性能需求

1.3.1 处理能力

根据项目的预期负载和网站的性质,应该选择适当的树莓派型号。不同型号的树莓派具有不同的处理器和内存规格,较高规格的型号可以处理更多的并发请求和复杂的任务。

1.3.2 内存

树莓派的内存(RAM)对于同时处理多个请求和运行内存密集型应用程序非常重要。较大的内存容量可以提高服务器的响应速度和性能。

1.3.3 存储空间

存储空间:考虑您需要存储的网站内容和相关文件的大小。选择合适的存储介质,如SD卡或外部硬盘驱动器,以满足您的存储需求。

2. 可行性分析

在命令行中使用cat /proc/cpuinfo后输出

renegadew@raspberrypi:~ $ cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 108.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstr                                                                                                                                           m crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd08
CPU revision    : 3

processor       : 1
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 108.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstr                                                                                                                                           m crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd08
CPU revision    : 3

processor       : 2
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 108.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstr                                                                                                                                           m crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd08
CPU revision    : 3

processor       : 3
model name      : ARMv7 Processor rev 3 (v7l)
BogoMIPS        : 108.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstr                                                                                                                                           m crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd08
CPU revision    : 3

Hardware        : BCM2711
Revision        : c03111
Serial          : 100000002b61e311
Model           : Raspberry Pi 4 Model B Rev 1.1

image3 cat /proc/cpuinfo的输出

由于里面没有MemTotal相关字段,故需要安装lshw来查看内存情况。

### lshw通常没有默认安装的,需要手动安装。
### 第一步先更新
sudo apt update
### 第二部安装lshw
sudo apt install lshw
### 第三步执行lshw命令
sudo lshw -short | grep memory

输入该命令后输出了/0/5 memory 3838MiB System memory即内存存量为3838MiB(3.8G)。

2.1 硬件方面

使用的树莓派的型号是Raspberry Pi 4 Model B Rev 1.1,并且配备了ARMv7架构的处理器,它具有相对较强的处理能力和内存支持,通常可以胜任轻量级的Web服务器任务。

  • 处理器:BCM2711,四核ARM Cortex-A72,时钟速度1.5 GHz
  • 内存:树莓派配置了3838MiB(约3.8GB)的系统内存。
  • 网络:支持千兆以太网接口,可提供稳定的网络连接。
  • 存储:通过MicroSD卡扩展存储容量,并连接外部存储设备,如USB驱动器。
  • GPU:集成的VideoCore VI图形处理器,可提供硬件加速和视频解码支持。
  • USB接口:具有多个USB 3.0和USB 2.0接口,可连接外部设备。
  • HDMI接口:支持连接显示器或电视以显示图形界面。
  • GPIO引脚:提供了多个GPIO引脚,可用于与外部设备的交互。

综上所示对于一般的Web服务器需求,树莓派4 Model B是一个经济实惠且功能强大的选择。

2.2 软件方面

  • 操作系统:树莓派常用的操作系统是基于LinuxRaspberry Pi OS(以前称为Raspbian)。它是专门为树莓派设计的操作系统,内置了许多优化和适配。可以从树莓派官方网站下载并安装Raspberry Pi OS

  • Web服务器软件:您可以选择安装常见的Web服务器软件,如ApacheNginxLighttpd。这些软件在树莓派上都有相应的版本,并且支持常见的Web技术和协议,如HTTP和HTTPS。

  • 文件传输:使用FileZilla已经ssh通讯来完成树莓派和本地电脑之间的文件传输,从而达到不需要在树莓派中写web开发的相关代码,直接在本地windows电脑上写好后传到树莓派上即可。

  • 内网穿透:订阅了ngrok的personal类型的账号,从而可以达到自定义域名的目的。

    image-20230629013402528

image4 FileZilla软件UI

3. 项目计划

整个项目大致分为三个阶段:前期、中期和后期。

3.1 前期

  1. 了解树莓派的硬件配置
  2. 将各个配件连接至树莓派
  3. 学习Linux命令
  4. 选择项目大致的方向。

3.2 中期

  1. 熟悉并使用PuTTYFileZilla等软件将树莓派和本地电脑连接起来
  2. 在树莓派电脑上安装相关的依赖以及软件包
  3. 开启树莓派ssh服务
  4. 在本地电脑上简单设计一个html静态页面通过apache2部署到树莓派中使得其在局域网中可以访问

3.3 后期

  1. 使用ngrok进行内网穿透以及域名的自定义,将静态页面仅被局域网中的用户可见拓展到在公网可见。
  2. 使用vue 2技术搭建一个网站的首页,使用node.js调用chatgpt接口部署自己的chatgpt网站,midjourney同理。
  3. 使用vue build打包将获得的dist上传到树莓派服务器上。

4. 初级阶段设计记录

4.1 了解树莓派的硬件配置

Raspberry Pi 4 Model B Rev 1.1拥有如下配置

  • BCM2711芯片

  • 搭载四核ARM Cortex-A72处理器

  • 内存是4GB的

  • 内置了VideoCore VI图形处理器,支持OpenGL ES 3.x和硬件加速的视频解码

  • 提供了两个特殊的HDMI接口

  • 它支持千兆以太网接口

  • 并且具有两个USB 3.0接口和两个USB 2.0接口

  • 具有双频(2.4 GHz和5 GHz)802.11ac无线局域网(Wi-Fi)和蓝牙5.0,可以无线连接到网络和外部设备。

微信截图_20230629100503

image5 Raspberry Pi 4 Model B Rev 1.1硬件图

4.2 将各个配件连接至树莓派

该项目将要用到的硬件设施如下

  • 一台显示器
  • usb接口的鼠标
  • usb接口的键盘
  • 千兆网线
  • 特殊的HDMI线
整体连接的实物图如下

微信图片_20230629102211

image6 整体连接的实物图

树莓派硬件情况如下

微信图片_20230629102217

image7 树莓派硬件连接情况1

image7 树莓派硬件连接情况2

微信图片_20230629102226

image7 树莓派硬件连接情况3

4.3 学习Linux命令

以下将列出在做项目过程中学习并使用到的所有命令

sudo apt-get update //更新Ubuntu系统软件包列表
sudo apt upgrade //升级 Ubuntu 系统中已安装软件包

sudo apt install nodejs //安装nodejs
sudo apt install npm //安装npm
sudo apt-get install <packageName> //用于在 Ubuntu 系统中安装软件包
sudo apt-get install raspi2png //安装raspi2png

sudo raspi-config//打开Raspberry Pi 配置工具

sudo raspi2png -p screenshot.png //使用raspi2png进行页面截图

ifconfig //显示ip地址 
ip addr show //显示ip地址
whoami //显示当前登录用户的用户名
sudo passwd <username> //更改用户的密码
sudo nft list ruleset //查看防火墙规则
sudo service ssh status //查看ssh服务是否在运行
sudo service ssh start //开启ssh服务

ls //当前文件夹中的所有文件和子文件夹的列表
ls -l //长格式列出文件和文件夹,并包括它们的权限、所有者、大小、修改日期
ls -a //括以点开头的隐藏文件和文件夹。

sudo tar xvzf /ngrok-v3-stable-linux-amd64.tgz -C /user/local/bin //n解压grok文件到/user/local/bin目录中
sudo nautilus //以管理员身份运行文件管理器


sudo unzip /path/to/ngrok.zip //解压某个文件
sudo ngrok config add-authtoken <your authoken> //为ngrok配置token
sudo ./ngrok http 80 //使用ngrok启动树莓派服务器的80端口

cd /etc/apache2/ //跳转到指定的文件目录下
sudo vim ports.conf //用管理员的身份进入ports.conf文件进行修改

sudo service apache2 restart //重启apache2服务

4.3 所遇到的问题

4.3.1树莓派开启ssh服务

简述:
本地电脑连接树莓派需要树莓派开启ssh服务,先前一直连接不上树莓派有个原因就是树莓派的ssh服务没有开启。

解决方法:

(这里我就不在树莓派上演示了,用远程的windows电脑操控它,这样好截图)

  1. 输入sudo raspi-config进入配置树莓派配置页面

    image-20230629123036754

image8 输入sudo raspi-config命令示意图
  1. 选择第三个Interface Options选项

    image-20230629123047707

image9 sudo raspi-config后示意图

  1. 选择第二个SSH选项

    image-20230629123158040

image10 Interface Options示意图

  1. 选择选项

    image-20230629123238293

image11 SSH示意图

这样就开启了ssh服务了,值得注意的是树莓派初始的ssh管理是比较松的。

  1. 使用sudo service ssh status查看ssh服务是否开启

    image-20230629123401204

image12 验证ssh是否开启
  1. Active显示绿色的active(running)即为开启成功

    image-20230629123515918

image13 验证ssh是否开启结果图
4.3.2使用Windows自带的远程桌面连接失败

前提:

  • 在开启了树莓派的ssh连接

    image-20230629123515918

image14 验证ssh是否开启结果图

  • 查看了其ip地址

    image-20230629120344673

image15 查看树莓派ip地址结果图
  • 拥有了树莓派用户的账号密码

    image-20230629120425863

image16 树莓派账号和密码

  • 在同一个局域网内

结果:

使用该软件远程连接树莓派失败,显示异常

image-20230629115954051

image17 远程桌面链接失败

分析:

  • 树莓派的操作系统不是windows

  • 操作系统版本早于兼容版本

    image-20230629120557339

image18 可能原因

解决办法:

使用PuTTY来进行远程连接(仅命令行)

  • 本地电脑下载并安装`PuTTY**

  • 输入树莓派ip地址并填写端口号(默认22)

    image-20230629121124930

image19 PuTTY软件示意图
  • 输入树莓派账号密码

    image-20230629121200551

image20 连接后输入账号密码

显示如下页面即为连接成功

image-20230629121248179

image21 连接成功截图

注意:请确保本机与树莓派在同一局域网内,并且树莓派开启了ssh

5. 中级阶段设计记录

5.1熟悉并使用相关软件

5.1.1 PuTTY
  1. 简单介绍

PuTTY是一款集成虚拟终端、系统控制台和网络文件传输为一体的自由及开放源代码的程序。它支持多种网络协议,包括SCP,SSH,Telnet,rlogin和原始的套接字连接。——wikipedia

  1. 操作

PuTTY软件的使用在初级阶段记录中有相关描述在此就不再赘述

  1. 下载地址

https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

5.1.2 FileZilla
  1. 简单介绍

FileZilla是一种免费的FTP客户端以及服务器端开放源代码程序,具有多种平台的软件包,可以在Windows,Linux和macOS平台上运行。服务器和客户端都支持FTP和FTPS,而客户端还可以连接到SFTP服务器。

  1. 操作
  • 在本机电脑下载并安装合适的版本

  • 打开FileZilla并填写相关字段

    image-20230629125248598

image22 FileZilla软件示意图
  • 建立连接后右边会显示出树莓派相关文件

    image-20230629125400416

image23 连接成功示意图
  • 可右键点击文件选择**上传(U)**来传输文件

    image-20230629132425331

image24 上传文件示意图

如图例,点击上传了后将会将左边全部所选文件传输到右边文件夹里面。

5.2 安装相关的依赖以及软件包

使用下列命令来给树莓派进行安装相关的软件包

sudo apt install nodejs 
sudo apt install npm 
sudo apt-get install raspi2png 
sudo apt-get install apache2

可以使用node -vnpm -v来查看npm和node是否安装成功

image-20230629130346287

image25 查看npm和node版本

5.3 开启树莓派ssh服务

该部分在4.4.1树莓派开启ssh服务中有具体的阐述,在此不再赘述。

5.4 简单的部署

在执行了5.2中的sudo apt-get install apache2命令后会在树莓派的/var/www/html生成一个apache的初始页面index.html文件。

放置在这里面的html文件都会被部署到树莓派服务器上。(前提是你的apach在运行中),下面这张图是我部署了打包后的vue项目,并非初始的index.html,一开始只有一个index.html文件。

image-20230629131410001

image26 安装apache2后index.html文件存放位置示意图

在浏览器上输入你的树莓派ip地址,这里我的树莓派ip地址是10.141.42.21,后会看到下面这个页面。

image-20230629131831896

image27 输入树莓派ip地址后页面示意图

至此简单的部署就完成了,在你所属的局域网的所有用户均可以通过使用浏览器访问树莓派的ip地址来看树莓派这个web服务器上面部署了什么页面。

值得注意的是仅限于局域网内,并非公网可访问的。

5.5 所遇到的问题

5.5.1 FileZilla传输文件时权限不够

问题背景:
想要部署自己的页面到树莓派服务器上主要有两步

  1. 删除原本的index.html相关文件
  2. 将新的index.html相关文件通过FileZilla上传到/var/www/html

在使用FileZilla进行上传和删除操作的时候会报错,显示权限不过。

解决方法:

在树莓派图形界面或直接在FileZilla中将html文件的修改权限从user->all,开放html文件夹里面的所有文件的更改权限

image-20230629134140917

image28 解决权限不够的方法示意图
5.5.2 访问受限

问题描述:

该web仅有在用户所用网络与树莓派在同一局域网下才可访问部署在树莓派上的web,假如用户使用别的网络去浏览器访问树莓派的ip地址10.141.42.21,会显示连接不上

解决办法:

使用内网穿透技术使得部署在树莓派上的web项目暴露在公网中供用户访问,将考虑使用ngrokngrok-ittunnginx等工具

6. 高级阶段设计记录

6.1 使用ngrok进行内网穿透

  1. 进入ngrok官网https://ngrok.com/

  2. 登录或者注册一个账号,这里我用Github注册了一个账号

    image-20230629134844716

image29 ngrok官网
  1. 为树莓派下载 Linux(ARM) 版本的ngrok

    image-20230629134957596

image30 ngrok下载
  1. 下载完毕后通过FileZilla将压缩包传到树莓派上

    image-20230629135326042

image31 传输ngrok压缩包
  1. 使用unzip /path/to/ngrok.zip为ngrok-v3-stable-linux-arm .tgz解压

  2. 在命令行中使用ngrok config add-authtoken <your authoken>为ngrok配置账号token

  3. 使用./ngrok http 80来启动ngrok,默认端口是80端口

    image-20230629140154397

image32 启动ngrok成功页面

启动后可以访问Forwarding后面的网址https://f4916595f05a.ngrok.app来访问树莓派上部署的web,不需要在跟树莓派同一局域网中就可以访问到该web了。

6.2 优化ngrok

单独只是这样还不够优雅,ngrok除了支持最基本的内网穿透还提供很多增值服务,其中选定服务器地区Region和二级域名subdomain功能都可以应用

  1. 登录ngrok官网进入仪表盘中的Cloud Edge选项卡中的Domains里,在这里你可以提前预定一个或者多个专属二级域名

    image-20230629141223151

image33 设置二级域名
  1. 点击右边的New Domain选项并输入你喜欢的二级域名

    image-20230629141403809

image34 设置二级域名
  1. 设置完成后,你可以使用./ngrok http --region=ap --domain=hiddensharppi.ap.ngrok.io 9000来部署你的网址

    image-20230629141921208

image35 启动带有二级域名的ngrok

这条指令有些参数是可选的,比如--region --domain以及后面的9000这个端口,具体的参数列表可参考官方中文文档http://ngrok.cn/docs.html#subdomain

6.3 用vue2搭建一个网站

该网站是用vue2 + node.js + js + vue cil来构建的,源代码会打包在一个名为pi-website的压缩包里面,同时在附录部分也会列出来。注意,由于考虑到树莓派的性能问题,故只完成了文件图片两个模块,聊天模块被单独分离出来,详情请看chatgpt-web,聊天模块暂时还没有开发出来。

6.3.1 pi-website项目层次结构
  • dist:build生成的文件

  • src:源代码目录

    • assets:静态资源目录
    • components:组件目录
      • ChatingModule.vue:聊天模块
      • DrawingModule.vue:绘画模块
      • FileModule.vue:文件模块
      • ImageModule.vue:图片模块
    • views:视图目录
      • Home
    • App.vue:根组件
    • main.js:入口文件
  • babel.config.js:Babel配置文件

  • package.json:项目配置文件

  • vue.config.js:Vue配置文件

6.3.2 chatgpt-web简单说明

​ 原本是想将chatgpt和midjourney部署到树莓派服务器的,但是由于后端开发的经验较少,并且树莓派的硬件承受不起这两个模块的并发运行,故只能将本地部署chatgpt的vue项目打包成一个rar放到服务器上了。若想要在本地部署chatgpt可按照下方这个简易的操作步骤来,也可参考压缩吗里面的README文件。

  1. 安装node和npm
  2. 安装pnmp: npm install pnpm -g
  3. 在终端打开chatgpt-web,输入命令pnpm bootstrap
  4. 添加一个新的终端CD进入services文件夹,输入pnpm install
  5. 在OPENAI_API_KEY后面填写密钥
  6. 启动魔法,尽量使用chatgpt支持的地区
  7. 在SOCKS_PROXY_HOST填写服务器的ip地址,可以使用本地主机
  8. 在SOCKS_PROXY_PORT填写魔法的端口号
  9. 配置完后在services的终端输入pnpm start启动后端
  10. 不要关闭后端的命令行,重新打开前端目录chatgpt-web,输入命令pnpm dev即可运行

image-20230629162256621

image36 成功部署本地chatgpt网站

6.4 部署到树莓派上

  1. 进入pi-website目录使用npm run build对vue项目进行打包

    image-20230629162407995

image37 vue项目打包
  1. 使用FileZilla与树莓派建立通信,并将pi-website/dist里面的所有文件上传到树莓派的www/html目录下

    image-20230629162518253

image38 将打包的vue项目上传到树莓派
  1. 打开PuTTY,进入树莓派命令行页面使用sudo service apache2 restart重启apache2服务

    image-20230629162852505

image39 重启apache2
  1. 使用./ngrok http --region=ap --domain=hiddensharppi.ap.ngrok.io 9000启动ngrok服务,将本地9000端口(apache2所启动的端口)进行内网穿透,使得在外网的用户也可访问。

    image-20230629163052303

image40 启动ngrok内网穿透
  1. 在浏览器输入Forwarding后面的网址 https://hiddensharppi.ap.ngrok.io访问web,稍等片刻页面就展示出来了。

    image-20230629163236350

image41 访问带有二级域名的网址

6.5 展示

树莓派网址:https://hiddensharppi.ap.ngrok.io/#/drawing

image-20230629163349712

image42 网站截图1

image-20230629163404795

image43 网站截图2

image-20230629163414424

image44 网站截图3

image-20230629163420941

image45 网站截图4

6.6 所遇到的问题

6.6.1 nginx显示无法定位软件包

问题背景:

使用sudo apt-get install nginx显示无法定位软件包

解决办法:

  1. 先更新软件包列表sudo apt-get update
  2. apt-cache search nginx搜索相关可用软件包
  3. 随后再使用sudo apt-get install nginx

注:该项目最后没有使用nginx,还是无法正常安装。

6.6.2 国内ngrok服务ngrok-ittun不能使用

说明从网上了解ngrok-ittun内网穿透支持二级域名的修改,并且延迟较低,但是好像从2018年开始启用ngrok-ittun就会一直显示reconnecting资料来自于腾讯云开发者社区https://cloud.tencent.com/developer/article/1093830?areaSource=106002.12

6.6.3 80端口被占用

问题背景:

许多软件启动的进程均在80端口,因此在进行web的部署的时候可能会发生冲突。

解决方法:

将apache2服务默认端口设置成9000,启动ngrok时也启动9000端口

  1. 在树莓派命令行使用cd /etc/apache2/
  2. 使用sudo vim ports.conf,对ports.conf文件进行修改。
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 9000 //这里填写你想要它默认启动的窗口,原本是80

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
  1. 使用sudo service apache2 restart重启apache2

7. 附录

7.1 App.vue

<!--
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:56:45
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-28 18:10:54
 * @FilePath: \pi-website\src\App.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <body>
    <div class="main_page">
      <div
        class="page_header floating_element"
        style="display: flex; flex-direction: column; align-items: center"
      >
        <img
          src="./assets/img/bg.png"
          alt="Debian Logo"
          class="floating_element"
          style="
            width: 100px;
            height: 50px;
            position: absolute;
            top: 0;
            left: 0;
          "
        />
        <span class="floating_element" style="margin-left: 0px">
          PI website
        </span>
        <div
          class="module-container"
          style="
            width: auto;
            display: flex;
            flex-direction: row;
            margin-top: 10px;
          "
        >
          <router-link to="/image" class="module-link">
            <div class="module">
              <span>图片</span>
            </div>
          </router-link>
          <router-link to="/file" class="module-link">
            <div class="module">
              <span>文件</span>
            </div>
          </router-link>
          <router-link to="/chat" class="module-link">
            <div class="module">
              <span>聊天</span>
            </div>
          </router-link>
          <router-link to="/drawing" class="module-link">
            <div class="module">
              <span>绘画</span>
            </div>
          </router-link>
        </div>
        <router-view></router-view>
      </div>
    </div>
    <div class="validator"></div>
    <div class="tip" v-if="flag">
      <div class="first">welcome to my pi website</div>
      <div class="first">@author:hiddensharp</div>
      <div class="first">欢迎来到我的树莓派web服务器</div>
      <div class="close" @click="flag = false">×</div>
    </div>
  </body>
</template>

<script>
export default {
  name: "App",
  created() {
    setTimeout(() => {
      this.flag = false;
    }, 5000);
  },
  data() {
    return {
      flag: true,
    };
  },
};
</script>

<style scoped>
* {
  margin: 0px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}
body,
html {
  padding: 3px 3px 3px 3px;

  background-color: #d8dbe2;

  font-family: Verdana, sans-serif;
  font-size: 11pt;
  text-align: center;
}

div.main_page {
  position: relative;
  display: table;

  width: 800px;

  margin-bottom: 3px;
  margin-left: auto;
  margin-right: auto;
  padding: 0px 0px 0px 0px;

  border-width: 2px;
  border-color: #212738;
  border-style: solid;

  background-color: #ffffff;

  text-align: center;
}

div.page_header {
  height: 99px;
  width: 100%;

  background-color: #f5f6f7;
}

div.page_header span {
  margin: 15px 0px 0px 50px;

  font-size: 180%;
  font-weight: bold;
}

div.page_header img {
  margin: 3px 0px 0px 40px;

  border: 0px 0px 0px;
}

div.table_of_contents {
  clear: left;

  min-width: 200px;

  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.table_of_contents_item {
  clear: left;

  width: 100%;

  margin: 4px 0px 0px 0px;

  background-color: #ffffff;

  color: #000000;
  text-align: left;
}

div.table_of_contents_item a {
  margin: 6px 0px 0px 6px;
}

div.content_section {
  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.content_section_text {
  padding: 4px 8px 4px 8px;

  color: #000000;
  font-size: 100%;
}

div.content_section_text pre {
  margin: 8px 0px 8px 0px;
  padding: 8px 8px 8px 8px;

  border-width: 1px;
  border-style: dotted;
  border-color: #000000;

  background-color: #f5f6f7;

  font-style: italic;
}

div.content_section_text p {
  margin-bottom: 6px;
}

div.content_section_text ul,
div.content_section_text li {
  padding: 4px 8px 4px 16px;
}

div.section_header {
  padding: 3px 6px 3px 6px;

  background-color: #8e9cb2;

  color: #ffffff;
  font-weight: bold;
  font-size: 112%;
  text-align: center;
}

div.section_header_red {
  background-color: #cd214f;
}

div.section_header_grey {
  background-color: #9f9386;
}

.floating_element {
  position: relative;
  float: left;
}

div.table_of_contents_item a,
div.content_section_text a {
  text-decoration: none;
  font-weight: bold;
}

div.table_of_contents_item a:link,
div.table_of_contents_item a:visited,
div.table_of_contents_item a:active {
  color: #000000;
}

div.table_of_contents_item a:hover {
  background-color: #000000;

  color: #ffffff;
}

div.content_section_text a:link,
div.content_section_text a:visited,
div.content_section_text a:active {
  background-color: #dcdfe6;

  color: #000000;
}

div.content_section_text a:hover {
  background-color: #000000;

  color: #dcdfe6;
}

div.validator {
}

.tip {
  z-index: 10;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateY(-50%) translateX(-50%);
  width: 800px;
  height: auto;
  background-color: rgba(126, 126, 126, 0.6);
  border-radius: 20px;
  color: #fff;
  text-align: center;
  font-size: 40px;

  .first {
    margin-top: 40px;
  }

  .close {
    font-size: 60px;
    position: absolute;
    top: 0;
    right: 20px;
    cursor: pointer;
  }
}
.module-link {
  text-decoration: none;
  color: #9f9386;
}
</style>

7.2 main.js

/*
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:56:45
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-28 18:52:19
 * @FilePath: \pi-website\src\main.js
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import ImageModule from './components/ImageModule.vue'
import FileModule from './components/FileModule.vue'
import ChatModule from './components/ChatingModule.vue'
import DrawingModule from './components/DrawingModule.vue'

Vue.config.productionTip = false
Vue.use(VueRouter)

const routes = [
  { path: '/image', component: ImageModule },
  { path: '/file', component: FileModule },
  { path: '/chat', component: ChatModule },
  { path: '/drawing', component: DrawingModule }
]
const router = new VueRouter({
  routes
})

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

7.3 ImageModule.vue

<!--
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:58:44
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-28 22:21:58
 * @FilePath: \pi-website\src\components\imageModule.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div class="main_page">
    <div class="content_section floating_element">
      <div class="section_header section_header_red">
        <div id="about"></div>
        README FIRST
      </div>
      <div class="content_section_text">
        <p>
          <tt>PHOTO LIST</tt>里面是一些图片,可以点击<tt>download</tt>进行下载。也可以右击选择<tt>在新标签页中打开链接</tt>来查看图片。现在一共有六个样例图片,后期可进行添加或者删除。
        </p>
        <p></p>
      </div>
      <div class="section_header">
        <div id="changes"></div>
        PHOTO LIST
      </div>
      <div class="content_section_text">
        <div class="image-list" style="display: flex; flex-direction: column">
          <div v-for="image in images" :key="image.name" class="image-item">
            <p style="display: flex; flex-direction: column-reverse">
              {{ image.name }}
              {{ image.size }}
              <a :href="image.downloadUrl" download>Download</a>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "ImageModule",
  data() {
    return {
      images: [
        {
          name: "image1.png",
          size: "350 KB",
          downloadUrl: require("../assets/img/image1.png"),
        },
        {
          name: "image2.png",
          size: "46 KB",
          downloadUrl: require("../assets/img/image2.png"),
        },
        {
          name: "image3.png",
          size: "78 KB",
          downloadUrl: require("../assets/img/image3.png"),
        },
        {
          name: "image4.png",
          size: "419 KB",
          downloadUrl: require("../assets/img/image4.png"),
        },
        {
          name: "image5.png",
          size: "351 KB",
          downloadUrl: require("../assets/img/image5.png"),
        },
        {
          name: "image6.png",
          size: "449 KB",
          downloadUrl: require("../assets/img/image6.png"),
        },
      ],
    };
  },
};
</script>

<style scoped>
* {
  margin: 10px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}
body,
html {
  background-color: #d8dbe2;
  font-family: Verdana, sans-serif;
  font-size: 11pt;
  text-align: center;
}
div.main_page {
  position: relative;
  display: table;

  width: 800px;

  margin-bottom: 3px;
  margin-left: auto;
  margin-right: auto;
  padding: 0px 0px 0px 0px;

  border-width: 2px;
  border-color: #212738;
  border-style: solid;

  background-color: #ffffff;

  text-align: center;
}

div.table_of_contents {
  clear: left;

  min-width: 200px;

  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.table_of_contents_item {
  clear: left;

  width: 100%;

  margin: 4px 0px 0px 0px;

  background-color: #ffffff;

  color: #000000;
  text-align: left;
}

div.table_of_contents_item a {
  margin: 6px 0px 0px 6px;
}

div.content_section {
  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.content_section_text {
  padding: 4px 8px 4px 8px;

  color: #000000;
  font-size: 100%;
}

div.content_section_text pre {
  margin: 8px 0px 8px 0px;
  padding: 8px 8px 8px 8px;

  border-width: 1px;
  border-style: dotted;
  border-color: #000000;

  background-color: #f5f6f7;

  font-style: italic;
}

div.content_section_text p {
  margin-bottom: 6px;
}

div.content_section_text ul,
div.content_section_text li {
  padding: 4px 8px 4px 16px;
}

div.section_header {
  padding: 3px 6px 3px 6px;

  background-color: #8e9cb2;

  color: #ffffff;
  font-weight: bold;
  font-size: 112%;
  text-align: center;
}

div.section_header_red {
  background-color: #cd214f;
}

div.section_header_grey {
  background-color: #9f9386;
}

.floating_element {
  position: relative;
  float: left;
}

div.table_of_contents_item a,
div.content_section_text a {
  text-decoration: none;
  font-weight: bold;
}

div.table_of_contents_item a:link,
div.table_of_contents_item a:visited,
div.table_of_contents_item a:active {
  color: #000000;
}

div.table_of_contents_item a:hover {
  background-color: #000000;

  color: #ffffff;
}

div.content_section_text a:link,
div.content_section_text a:visited,
div.content_section_text a:active {
  background-color: #dcdfe6;

  color: #000000;
}

div.content_section_text a:hover {
  background-color: #000000;

  color: #dcdfe6;
}

div.validator {
}
</style>

7.4 FileModule.vue

<!--
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:59:08
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-29 15:27:03
 * @FilePath: \pi-website\src\components\FileModule.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div class="main_page">
    <div class="content_section floating_element">
      <div class="section_header section_header_red">
        <div id="about"></div>
        README FIRST
      </div>
      <div class="content_section_text">
        <p>
          <tt>PHOTO LIST</tt>里面是一些文件,可以点击<tt>download</tt>进行下载。也可以右击选择<tt>在新标签页中打开链接</tt>来查看文件。现在一共有六个样例文件,后期可进行添加或者删除。
        </p>
        <p></p>
      </div>
      <div class="section_header">
        <div id="changes"></div>
        File LIST
      </div>
      <div class="content_section_text">
        <div class="file-list" style="display: flex; flex-direction: column">
          <div v-for="file in files" :key="file.name" class="file-item">
            <p style="display: flex; flex-direction: column-reverse">
              {{ file.name }}
              {{ file.size }}
              <a :href="file.downloadUrl" download>Download</a>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "fileModule",
  data() {
    return {
      files: [
        {
          name: "file1.doc",
          size: "0 KB",
          downloadUrl: "/file/file1.doc",
        },
        {
          name: "file2.txt",
          size: "0 KB",
          downloadUrl: "/file/file2.txt",
        },
        {
          name: "file3.zip",
          size: "0 KB",
          downloadUrl: "/file/file3.zip",
        },
        {
          name: "file4.ppt",
          size: "0 KB",
          downloadUrl: "/file/file4.ppt",
        },
        {
          name: "file5.md",
          size: "0 KB",
          downloadUrl: "/file/file5.md",
        },
      ],
    };
  },
};
</script>

<style scoped>
* {
  margin: 10px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}
body,
html {
  background-color: #d8dbe2;
  font-family: Verdana, sans-serif;
  font-size: 11pt;
  text-align: center;
}
div.main_page {
  position: relative;
  display: table;

  width: 800px;

  margin-bottom: 3px;
  margin-left: auto;
  margin-right: auto;
  padding: 0px 0px 0px 0px;

  border-width: 2px;
  border-color: #212738;
  border-style: solid;

  background-color: #ffffff;

  text-align: center;
}

div.table_of_contents {
  clear: left;

  min-width: 200px;

  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.table_of_contents_item {
  clear: left;

  width: 100%;

  margin: 4px 0px 0px 0px;

  background-color: #ffffff;

  color: #000000;
  text-align: left;
}

div.table_of_contents_item a {
  margin: 6px 0px 0px 6px;
}

div.content_section {
  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.content_section_text {
  padding: 4px 8px 4px 8px;

  color: #000000;
  font-size: 100%;
}

div.content_section_text pre {
  margin: 8px 0px 8px 0px;
  padding: 8px 8px 8px 8px;

  border-width: 1px;
  border-style: dotted;
  border-color: #000000;

  background-color: #f5f6f7;

  font-style: italic;
}

div.content_section_text p {
  margin-bottom: 6px;
}

div.content_section_text ul,
div.content_section_text li {
  padding: 4px 8px 4px 16px;
}

div.section_header {
  padding: 3px 6px 3px 6px;

  background-color: #8e9cb2;

  color: #ffffff;
  font-weight: bold;
  font-size: 112%;
  text-align: center;
}

div.section_header_red {
  background-color: #cd214f;
}

div.section_header_grey {
  background-color: #9f9386;
}

.floating_element {
  position: relative;
  float: left;
}

div.table_of_contents_item a,
div.content_section_text a {
  text-decoration: none;
  font-weight: bold;
}

div.table_of_contents_item a:link,
div.table_of_contents_item a:visited,
div.table_of_contents_item a:active {
  color: #000000;
}

div.table_of_contents_item a:hover {
  background-color: #000000;

  color: #ffffff;
}

div.content_section_text a:link,
div.content_section_text a:visited,
div.content_section_text a:active {
  background-color: #dcdfe6;

  color: #000000;
}

div.content_section_text a:hover {
  background-color: #000000;

  color: #dcdfe6;
}

div.validator {
}
</style>

7.5 ChatingModule.vue

<!--
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:59:08
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-29 16:07:08
 * @FilePath: \pi-website\src\components\FileModule.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div class="main_page">
    <div class="content_section floating_element">
      <div class="section_header section_header_red">
        <div id="about"></div>
        README FIRST
      </div>
      <div class="content_section_text">
        <p>
          由于树莓派的硬件设施问题,暂不支持聊天模块,后期会可能会进行添加,下面是一个部署好了本地chatgpt的网站,详情请下载下面这个压缩包。
        </p>
        <p>简易教程</p>
        <p>1.安装node和npm</p>
        <p>2.安装pnmp: <code>npm install pnpm -g</code></p>
        <p>3.在终端打开chatgpt-web,输入命令<code>pnpm bootstrap</code></p>
        <p>4.添加一个新的终端CD进入services文件夹,输入<code>pnpm install</code></p>
        <p>5.在OPENAI_API_KEY后面填写密钥 若没有可以使用我的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</p>
        <p>6.启动魔法,尽量使用chatgpt支持的地区</p>
        <p>7.在SOCKS_PROXY_HOST填写服务器的ip地址,可以使用本地主机</p>
        <p>8.在SOCKS_PROXY_PORT填写魔法的端口号</p>
        <p>9.配置完后在services的终端输入<code>pnpm start</code>启动后端</p>
        <p>10.运行起来后若不确定是否启动成功可以访问ip:3002,若显示Cannot GET则说明成功</p>
        <p>11.不要关闭后端的命令行,重新打开前端目录chatgpt-web,输入命令<code>pnpm dev</code>即可运行  </p>
      </div>

      <div class="section_header">
        <div id="changes"></div>
        Source code
      </div>
      <div class="content_section_text">
        <div class="file-list" style="display: flex; flex-direction: column">
          <div v-for="file in files" :key="file.name" class="file-item">
            <p style="display: flex; flex-direction: column-reverse">
              {{ file.name }}
              {{ file.size }}
              <a :href="file.downloadUrl" download>Download</a>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "ChatingModule",
  data() {
    return {
      files: [
        {
          name: "chatgpt-web.rar",
          size: "593957 KB",
          downloadUrl: "/file/chatgpt-web.rar",
        },
      ],
    };
  },
};
</script>

<style scoped>
* {
  margin: 10px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}
body,
html {
  background-color: #d8dbe2;
  font-family: Verdana, sans-serif;
  font-size: 11pt;
  text-align: center;
}
div.main_page {
  position: relative;
  display: table;

  width: 800px;

  margin-bottom: 3px;
  margin-left: auto;
  margin-right: auto;
  padding: 0px 0px 0px 0px;

  border-width: 2px;
  border-color: #212738;
  border-style: solid;

  background-color: #ffffff;

  text-align: center;
}

div.table_of_contents {
  clear: left;

  min-width: 200px;

  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.table_of_contents_item {
  clear: left;

  width: 100%;

  margin: 4px 0px 0px 0px;

  background-color: #ffffff;

  color: #000000;
  text-align: left;
}

div.table_of_contents_item a {
  margin: 6px 0px 0px 6px;
}

div.content_section {
  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.content_section_text {
  padding: 4px 8px 4px 8px;

  color: #000000;
  font-size: 100%;
}

div.content_section_text pre {
  margin: 8px 0px 8px 0px;
  padding: 8px 8px 8px 8px;

  border-width: 1px;
  border-style: dotted;
  border-color: #000000;

  background-color: #f5f6f7;

  font-style: italic;
}

div.content_section_text p {
  margin-bottom: 6px;
}

div.content_section_text ul,
div.content_section_text li {
  padding: 4px 8px 4px 16px;
}

div.section_header {
  padding: 3px 6px 3px 6px;

  background-color: #8e9cb2;

  color: #ffffff;
  font-weight: bold;
  font-size: 112%;
  text-align: center;
}

div.section_header_red {
  background-color: #cd214f;
}

div.section_header_grey {
  background-color: #9f9386;
}

.floating_element {
  position: relative;
  float: left;
}

div.table_of_contents_item a,
div.content_section_text a {
  text-decoration: none;
  font-weight: bold;
}

div.table_of_contents_item a:link,
div.table_of_contents_item a:visited,
div.table_of_contents_item a:active {
  color: #000000;
}

div.table_of_contents_item a:hover {
  background-color: #000000;

  color: #ffffff;
}

div.content_section_text a:link,
div.content_section_text a:visited,
div.content_section_text a:active {
  background-color: #dcdfe6;

  color: #000000;
}

div.content_section_text a:hover {
  background-color: #000000;

  color: #dcdfe6;
}

div.validator {
}
</style>

7.6 DrawingModule.vue

<!--
 * @Author: hiddenSharp429 z404878860@163.com
 * @Date: 2023-06-28 14:59:08
 * @LastEditors: hiddenSharp429 z404878860@163.com
 * @LastEditTime: 2023-06-29 16:02:21
 * @FilePath: \pi-website\src\components\FileModule.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  <div class="main_page">
    <div class="content_section floating_element">
      <div class="section_header section_header_red">
        <div id="about"></div>
        README FIRST
      </div>
      <div class="content_section_text">
        <p>
          由于树莓派的硬件设施问题,暂不支持绘画模块,后期会可能会进行添加,本地draw的网站正在紧张的研发中,敬请期待。
        </p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "DrawingModule",
  data() {
    return {
    };
  },
};
</script>

<style scoped>
* {
  margin: 10px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}
body,
html {
  background-color: #d8dbe2;
  font-family: Verdana, sans-serif;
  font-size: 11pt;
  text-align: center;
}
div.main_page {
  position: relative;
  display: table;

  width: 800px;

  margin-bottom: 3px;
  margin-left: auto;
  margin-right: auto;
  padding: 0px 0px 0px 0px;

  border-width: 2px;
  border-color: #212738;
  border-style: solid;

  background-color: #ffffff;

  text-align: center;
}

div.table_of_contents {
  clear: left;

  min-width: 200px;

  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.table_of_contents_item {
  clear: left;

  width: 100%;

  margin: 4px 0px 0px 0px;

  background-color: #ffffff;

  color: #000000;
  text-align: left;
}

div.table_of_contents_item a {
  margin: 6px 0px 0px 6px;
}

div.content_section {
  margin: 3px 3px 3px 3px;

  background-color: #ffffff;

  text-align: left;
}

div.content_section_text {
  padding: 4px 8px 4px 8px;

  color: #000000;
  font-size: 100%;
}

div.content_section_text pre {
  margin: 8px 0px 8px 0px;
  padding: 8px 8px 8px 8px;

  border-width: 1px;
  border-style: dotted;
  border-color: #000000;

  background-color: #f5f6f7;

  font-style: italic;
}

div.content_section_text p {
  margin-bottom: 6px;
}

div.content_section_text ul,
div.content_section_text li {
  padding: 4px 8px 4px 16px;
}

div.section_header {
  padding: 3px 6px 3px 6px;

  background-color: #8e9cb2;

  color: #ffffff;
  font-weight: bold;
  font-size: 112%;
  text-align: center;
}

div.section_header_red {
  background-color: #cd214f;
}

div.section_header_grey {
  background-color: #9f9386;
}

.floating_element {
  position: relative;
  float: left;
}

div.table_of_contents_item a,
div.content_section_text a {
  text-decoration: none;
  font-weight: bold;
}

div.table_of_contents_item a:link,
div.table_of_contents_item a:visited,
div.table_of_contents_item a:active {
  color: #000000;
}

div.table_of_contents_item a:hover {
  background-color: #000000;

  color: #ffffff;
}

div.content_section_text a:link,
div.content_section_text a:visited,
div.content_section_text a:active {
  background-color: #dcdfe6;

  color: #000000;
}

div.content_section_text a:hover {
  background-color: #000000;

  color: #dcdfe6;
}

div.validator {
}
</style>

7.7 树莓派服务器网址

https://hiddensharppi.ap.ngrok.io/#/drawing

8. REFERENCE

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hiddenSharp429

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值