首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
2,529 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,147 阅读
3
解决 nginxProxyManager 申请证书时的SSL失败问题
657 阅读
4
Pointer-Focus:一款功能强大的教学、录屏辅助软件
635 阅读
5
使用cspell对项目做拼写规范检查
598 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
项目
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
frp
RabbitMQ
gitlab
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
朱治龙
累计撰写
146
篇文章
累计收到
9
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
项目
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
146
篇与
朱治龙
的结果
2023-08-17
自定义 docker 网络
docker 容器如果没有指定网络,在启动时会默认生成一个桥接的网络,如下图所示:启动的 docker 容器多了,再启动其他容器的时候,就会出现Error response from daemon: Pool overlaps with other one on this address space 的提示。为解决这个问题,可以采用手工创建桥接网络,然后在 docker-compose.yml 中指定网络的方式解决。{card-describe title="本地 docker 环境信息"}jiuzilong@jiuzilong:/data/dockerRoot/apps/chat2db$ docker -v Docker version 24.0.4, build 3713ee1 jiuzilong@jiuzilong:/data/dockerRoot/apps/chat2db$ docker compose version Docker Compose version v2.19.1 jiuzilong@jiuzilong:/data/dockerRoot/apps/chat2db$ {/card-describe}一、创建桥接网络:docker network create --subnet=192.168.100.0/16 --gateway=192.168.100.1 --opt "com.docker.network.bridge.name"="bridge_zzl" bridge_zzl也可以补充些其他网络配置信息:docker network create --subnet=172.66.0.0/16 --gateway=172.66.0.1 --opt "com.docker.network.bridge.default_bridge"="false" --opt "com.docker.network.bridge.name"="bridge_zzl" --opt "com.docker.network.bridge.enable_icc"="true" --opt "com.docker.network.bridge.enable_ip_masquerade"="true" --opt "com.docker.network.bridge.host_binding_ipv4"="0.0.0.0" --opt "com.docker.network.driver.mtu"="1500" bridge_zzl{message type="info" content="2024-09-11 更新:subnet 使用 192.168.100.0/16 网段,发现映射端口后仅宿主机可访问,换成 172.*.0.0/16 后,宿主机及局域网内的其他终端也都可访问,不知道是什么原因导致的。"/}如果创建失败,极有可能是网络数量超标了,可以停掉一些容器再创建。失败截图如下:如果创建成功,会响应创建成功的 NETWORK ID,如下图所示:二、docker-compose.yml 配置网络配置示例:version: '3' services: app: # image: 'jc21/nginx-proxy-manager:2.9.22' # image: 'chishin/nginx-proxy-manager-zh:latest' image: 'zhuzl/nginx-proxy-manager:ssl' restart: unless-stopped container_name: nginxProxyManager ports: - '80:80' - '81:81' - '443:443' networks: - net-zzl volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: net-zzl: name: bridge_zzl external: true 核心是networks: net-zzl: name: bridge_zzl external: true然后在 docker 应用中通过 networks 指定 networks: - net-zzl{alert type="warning"}特别注意:在拷贝上面代码到 docker-compose.yml 文件时,需要特别留意缩进问题{/alert}
2023年08月17日
103 阅读
0 评论
0 点赞
2023-08-11
ubuntu环境安装Harbor记录
Harbor 是一款优秀的开源企业容器镜像仓库。包括了基于web界面的权限管理(RBAC)、LDAP、审计、安全漏洞扫描、镜像验真、管理界面、自我注册、HA 等企业必需的功能,同时针对中国用户的特点,设计镜像复制和中文支持等功能。官网链接:https://goharbor.io/为方便应用开发部署,想要自己搭建Docker 镜像仓库,以下是部署过程。{card-describe title="Ubuntu版本情况"}zhuzl@zhuzl-M9-PRO:/data/software/harbor$ uname -a Linux zhuzl-M9-PRO 6.2.0-26-generic #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 16:27:29 UTC 2 x86_64 x86_64 x86_64 GNU/Linux{/card-describe}一、下载安装包根据Harbor官网的引导,可通过github的发布页面下载离线安装包:https://github.com/goharbor/harbor/releases由于国内网络下载速度堪忧,本次下载使用 ghproxy.com 进行代理加速下载,也就是在下载地址前添加 https://ghproxy.com/。下载记录如下:zhuzl@zhuzl-M9-PRO:/data/software$ wget https://ghproxy.com/https://github.com/goharbor/harbor/releases/download/v2.8.4/harbor-offline-installer-v2.8.4.tgz --2023-08-18 08:48:42-- https://ghproxy.com/https://github.com/goharbor/harbor/releases/download/v2.8.4/harbor-offline-installer-v2.8.4.tgz 正在解析主机 ghproxy.com (ghproxy.com)... 192.9.132.155 正在连接 ghproxy.com (ghproxy.com)|192.9.132.155|:443... 已连接。 已发出 HTTP 请求,正在等待回应... 200 OK 长度: 608175520 (580M) [application/octet-stream] 正在保存至: ‘harbor-offline-installer-v2.8.4.tgz’ harbor-offline-installer-v2.8.4.tgz 100%[==============================================================================================>] 580.00M 4.40MB/s 用时 2m 9s 2023-08-18 08:50:52 (4.49 MB/s) - 已保存 ‘harbor-offline-installer-v2.8.4.tgz’ [608175520/608175520]) zhuzl@zhuzl-M9-PRO:/data/software$ 二、解压安装包文件下载下来是一个以 .tgz 格式结尾的压缩文件,我们可以直接使用 tar 解压,解压命令为(需替换X.Y.Z为下载对应的版本):tar -zxvf harbor-offline-installer-vX.Y.Z.tgz解压记录如下:zhuzl@zhuzl-M9-PRO:/data/software$ tar -zxvf harbor-offline-installer-v2.8.4.tgz harbor/harbor.v2.8.4.tar.gz harbor/prepare harbor/LICENSE harbor/install.sh harbor/common.sh harbor/harbor.yml.tmpl zhuzl@zhuzl-M9-PRO:/data/software$ 三、配置及安装搜了下网上的课程,大多都是从配置证书开始的,我部署应用时一般都是应用开启http服务,使用nginx代理的时候再使用SSL证书对外提供https的访问。3.1 配置拷贝解压出来的 harbor.yml.tmpl 文件为 harbor.yml。修改这个配置文件。主要有如下信息需要修改的地方:hostname: 修改为域名https: 根据需要配置,我本处是直接注释掉了harbor_admin_password:管理员密码data_volume: harbor数据目录,这是宿主机的文件目录修改后执行初始化:./prepare初始化日志记录日下:zhuzl@zhuzl-M9-PRO:/data/dockerRoot/apps/harbor$ ./prepareprepare base dir is set to /data/dockerRoot/apps/harborWARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to httpsGenerated configuration file: /config/portal/nginx.confGenerated configuration file: /config/log/logrotate.confGenerated configuration file: /config/log/rsyslog_docker.confGenerated configuration file: /config/nginx/nginx.confGenerated configuration file: /config/core/envGenerated configuration file: /config/core/app.confGenerated configuration file: /config/registry/config.ymlGenerated configuration file: /config/registryctl/envGenerated configuration file: /config/registryctl/config.ymlGenerated configuration file: /config/db/envGenerated configuration file: /config/jobservice/envGenerated configuration file: /config/jobservice/config.ymlGenerated and saved secret to file: /data/secret/keys/secretkeySuccessfully called func: create_root_certGenerated configuration file: /compose_location/docker-compose.ymlClean up the input dirzhuzl@zhuzl-M9-PRO:/data/dockerRoot/apps/harbor$初始化完成后,会在当前目录生成 `docker-compose.yml`文件,此时,我们可以使用 `docker compose` 启动docker 服务啦,日志记录如下:zhuzl@zhuzl-M9-PRO:/data/dockerRoot/apps/harbor$ sudo docker compose up -d[+] Running 9/9 ✔ Container harbor-log Started 0.3s ✔ Container redis Started 0.7s ✔ Container registry Started 1.1s ✔ Container registryctl Started 0.9s ✔ Container harbor-portal Started 0.8s ✔ Container harbor-db Started 1.1s ✔ Container harbor-core Started 1.5s ✔ Container nginx Started 2.0s ✔ Container harbor-jobservice Started 2.1szhuzl@zhuzl-M9-PRO:/data/dockerRoot/apps/harbor$ 对了,执行`docker compose`的时候一定要用`sudo`,否则会出现文件权限相关的报错。 启动完成后,可以通过 `nginx` 配置代理的方式对外提供服务,配置好后,访问出现如下图所示的登录界面: ![harbor 登录界面](https://blog.zhuzhilong.cn/usr/uploads/2023/08/2381568293.png) 在登录界面可以使用管理员账号 admin,进行登录,密码为在`harbor.yml` 文件中`harbor_admin_password` 配置的默认密码。 登录成功后,主界面如下图所示: ![Harbor主界面](https://blog.zhuzhilong.cn/usr/uploads/2023/08/3334557343.png)
2023年08月11日
79 阅读
0 评论
0 点赞
2023-07-19
使用cspell对项目做拼写规范检查
项目中的拼写问题主要涉及变量命名、方法名、样式名等字符串的规范,使用cspell能在很大程度上减少由于输入或记错导致的错误单词的出现,也能让命名更规范更易于理解。这篇文字是在我们已有项目的实践。
2023年07月19日
598 阅读
0 评论
0 点赞
2023-07-13
通过Docker Compose安装Jira
由于数据库在服务器上已提前安装好,本处省略MySQL的安装流程。环境说明Jira 相关的文件统一放到 /data/dockerRoot/jira 目录。docker-compose.yml 文件内容如下:version: '3.9' services: jira: container_name: jira image: atlassian/jira-software:latest restart: "no" ports: - 18080:8080 environment: CATALINA_OPTS: -javaagent:/opt/atlassian/jira/atlassian-agent.jar volumes: - ./jira_data:/var/atlassian/application-data/jira - ./libs/atlassian-agent.jar:/opt/atlassian/jira/atlassian-agent.jar - ./libs/mysql-connector-java-8.0.30.jar:/opt/atlassian/jira/lib/mysql-connector-java.jar - ../hosts:/etc/hosts - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:rolibs 目录的文件打包如下:libs.zip启动服务sudo docker compose up -d设置JiraJira 容器启动完毕后,可通过 http://localhost:18080 访问,会自动调整到如下图所示的初始化向导页面:1、设置为中文单击 右上角的「Language」2、我将设置它自己在第二步选择「我将设置它自己」3、数据库连接配置连接数据库选择「其他数据库」,数据库类型根据本地环境进行选择,选择对应的数据库,要提前引入对应数据库的驱动 jar 包。4、设置应用程序属性5、设置许可证如果本地有Java 环境,可以在本地生成许可证,没有的话进入Jira 容器生成也可以执行如下命令,替换对应的服务器ID:java -jar atlassian-agent.jar -d -m test@test.com -n BAT -p jira -o lewis2951 -s B87T-QH0H-UBTM-IU5Q以上命令相关说明如下:java -jar atlassian-agent.jar \ -m zh_season@163.com # Licence Emali \ -n atlassian # Licence Name \ -o atlassian # Licence organization \ -p crowd # Licence product, support: crowd, conf, jira, bitbucket \ -s <copy from website> # License server id以上通过本地环境生成,复制许可证内容到输入框。6、设置管理员根据自己需求设置,Email 可以是一个不存在的,但是建议使用真实Email。7、设置电子邮件通知8、完成部署到此,Jira 就部署完成了。进入欢迎页面,可以创建一个示例项目,9、查看许可证在管理 → 应用程序下可查看许可证信息
2023年07月13日
43 阅读
0 评论
0 点赞
2023-07-01
常用docker 命令
该文档内容主要用于日常记录,会逐步添加重启 docker 服务sudo systemctl daemon-reload sudo systemctl restart docker停止所有运行中的容器docker ps -q | xargs docker stop将当前用户添加到docker用户组,那样就不用每次执行docker命令都加sudosudo usermod -aG docker $USER复制容器中的目录到本地:sudo docker cp <CONTAINER_ID>:/usr/local/tomcat/webapps/ROOT ./temp进入容器sudo docker exec -it mongodb /bin/bash创建网络docker network create --driver=bridge --subnet=192.168.0.0/16 bridge_zzl构建镜像docker build -f ./Dockerfile.devIstio -t console-mobile-ui:0.0.1 .将容器保存为新镜像sudo docker commit nginxProxyManager zhuzl/nginx-proxy-manager:2.11.1-ssl将其他仓库的 docker 镜像推送到本地私服一般用于本地下载外网镜像超级慢的情况,可找台外网的机器 pull,然后 push 到 Docker 私服docker pull ghcr.io/huolalatech/page-spy-web:release docker tag ghcr.io/huolalatech/page-spy-web:release xxx.yyy.zhuzhilong.com/apps/page-spy-web:release docker push xxx.yyy.zhuzhilong.com/apps/page-spy-web:release删除所有未运行的容器;运行的删除不了docker rm $(docker ps -a -q)根据容器的状态删除状态为Exited的容器docker rm $(docker ps -qf status=exited)查看docker日志占用情况及日志清理# /etc/docker/daemon.json 中的 "data-root": "/data/dockerRoot/dataRoot" sudo ls -lh $(sudo find /data/dockerRoot/dataRoot/containers/ -name *-json.log) cat /dev/null > /data/dockerRoot/dataRoot/containers/e876d8da919db8905dece519a81ecc182bc918c20397e5212f2b49e06ec03a01/e876d8da919db8905dece519a81ecc182bc918c20397e5212f2b49e06ec03a01-json.log删除所有tag中带 “none” 关键字的镜像#!/bin/bash # docker rmi $(docker images | grep "none" | awk '{print $3}') TAG=`docker images | grep none| awk '{print $3}'` for tag in $TAG do docker rmi -f $tag done exit 使用 prune 命令删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像docker image prune
2023年07月01日
63 阅读
0 评论
0 点赞
2023-06-29
处理360路由器不能通过代理在外网访问的问题
缘起2020年的时候,感觉家里的路由实在是太慢了,正好360新出的360WiFi6全屋路由 天穹 V6 路由感觉还蛮不错,就入手了。 使用至今整体还算稳定,功能也能满足大部分使用场景,而且系统自带「自定义hosts」功能,在内网想要使用域名访问部署的服务也就方便多了,虽说不支持泛域名解析,一个一个配有些繁琐,但好歹也省去另外部署DNS的繁琐操作了。只是官方自带的 app 在功能上有不少阉割,像「功能扩展」下的大部分功能就只能通过PC 网页端访问。今年618屯了台配置还不错的迷你主机,准备放在家里长期开机做内网开发服务器用。当然作为一个程序员长期开机的机器,内网穿透肯定是要部署的。上下求索所有准备工作就绪后,想着方便随时配置路由,便把路由通过内网穿透在外网可以随时远程访问。配置好FRP和nginx代理后,登录页面可以正常访问,但是登录后,跳转到主界面,闪一下便又回到登录界面了。问题既然出了,先看看有没有碰到同样问题的小伙伴。便通过360路由官方的社区链接,看看有没有用户反馈相同的问题,于是找到下面这几个反馈同类问题的帖子:360路由P2还是不能通过外网 wan口登录管理?外网怎么访问路由器后台如何在外网下访问路由器的登录界面P1好像不能在外网登录管理页面嘛!!看了一圈,始终没有一个能解决问题的答复,而且看一些标注为「产品答疑师」的回复都不能解决实际反馈的问题,看来是时候施展混迹IT圈多年所学的三脚猫功夫啦。毕竟原理上都是HTTP访问,浏览器发起的HTTP请求只要跟内网请求头信息一致,理论上都是可行的。开始折腾于是通过nginx 代理的地址,真发现如下问题:登录后,发起了「GET /router/get_router_device_capability.cgi」ajax 请求,但是,该接口响应的是302,又重定向到/login.htm登录页面。初步以为是后端对 Host 或 Referer这些头信息做了校验,遂配置nginx相关头信息: proxy_set_header Host http://192.168.0.1; proxy_set_header Referer http://192.168.0.1/login_pc.htm;重启 nginx 再次访问,发现问题依旧。进一步发现请求头中有一个token_id的头信息,值为 undefined,根据 token_id 字符串搜了下网页代码,发现从url获取参数存在问题:该导致了请求的时候根本没获取到用户认证所需的 token_id,导致请求失败。进一步深挖,发现头信息中的 Cookie 是存在 token_id 的:这样的话,是不是可以考虑nginx代理的时候,从Cookie中获取token_id然后设置一个 名为token_id的头请求后端呢?说干就干,一翻摸索后,在nginx配置信息中加入如下信息:set $TOKEN_ID ""; if ($http_cookie ~* "token_id=(.+?)(?=;|$)") { set $TOKEN_ID "$1"; } proxy_set_header token_id "$TOKEN_ID";完整nginx虚拟主机配置信息如下:server { listen 80; server_name router.home.zhuzhilong.cn; location / { proxy_set_header Referer http://192.168.0.1/login_pc.htm; proxy_pass http://192.168.0.1; set $TOKEN_ID ""; if ($http_cookie ~* "token_id=(.+?)(?=;|$)") { set $TOKEN_ID "$1"; } proxy_set_header token_id "$TOKEN_ID"; } }重启路由器,验证成功!!圆满收场最终实现后的效果录屏如下:{dplayer src="/usr/uploads/2023/06/4228503511.mp4"/}
2023年06月29日
274 阅读
0 评论
0 点赞
2023-06-24
将vue-cli搭建的Vue前端工程更新构建工具为pnpm+vite经验分享
缘起我们团队负责的超算云服务控制台前端工程(console-ui)从提交第一行代码(2021-03-18)至今已 2 年多时间,两年多的时间内随着需求的增加,前端工程也越来越庞大,陈旧的基础设施暴露出了如下问题:1、Node.js 版本仍然使用 2021 年发布的 14.x,而最新的 Node 大版本已更新到20.x,仍然使用旧版本导致一些构建工具版本不能随意更新,也同时留下一定的安全隐患。2、项目目前仍使用两年前发行的 Webpack4.46.0 版本作为项目构建工具,随着项目逐渐庞大,Webpack 在开发时编译项目时间通常达到 2 分钟以上,严重影响开发效率。3、前端生态日新月异,守着老旧的生态,不便于团队成长。借本次升级 console-ui 核心 vue版本的契机,考虑同时升级到新的构建工具。Vite 从 2021 年发布以来,在构建速度及开发体验上一直口碑不错,经过两年多的迭代,也趋于稳定,完全可以应用于产线了。且根据官方的介绍 Vite 4.3 开始的版本比之前的版本在开发及构建速度上又有了成倍的提升:发行日志见链接:https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md开始折腾1、升级 Node.js目前 console-ui 构建还是用的Node.js 14.x, 而现在 Node.js 的 LTS 版本是18.x,按照 Node.js 的版本发行规律,预计目前最新的 20.x 版本在今年的 10 月份将成为下一个 LTS 版本,且 Node.js 的向下兼容性较强,所以目前咱们可以拥抱 Node.js 20.x 相关生态。所以本次升级就直接选用 Node.js 20.x 了。开发机是使用nvm管理 Node.js 多版本的。使用如下命令安装并使用Node.js 20:nvm install 20.3.0 nvm use 20.3.02、升级构建工具在升级过程如下:创建初始工程使用官方开始页面提供的命令初始化一个初始工程:pnpm create vite console-ui --template vue3、console-ui 已有功能最简验证3.1、支持多主题console-ui的设定是可以打包不同主题的页面,构建时根据 env 变量打包不同主题的版本,然后采用不同主题文件。经验证vite也支持,vite.config.js 核心配置代码如下:css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/themes/${env.VITE_APP_DIST_MODE}.scss";` } } }由于不同的主题有些不同的资源文件(如favicon.ico、主题相关的图片等)需要引用,在这里我们在public 目录下新建2个目录用于管理不同主题资源文件的管理,然后配置不同的publicDir:publicDir: `./public/${distMode}`,3.2、验证多页面支持console-ui提供针对普通用户的 UI 界面,同时也提供管理员使用的维护界面,这两套界面在原工程是使用多页面机制实现的。经验证,vite 也能很好的支持,核心配置代码如下:plugins: [ createHtmlPlugin({ minify: true, pages: [ { entry: '/src/main.js', filename: 'index.html', template: 'index.html', injectOptions: { data: { title: '超算云服务控制台' } } }, { entry: 'src/pages/admin/index.js', filename: 'admin.html', template: 'admin.html', injectOptions: { data: { title: '超算运服务控制台-系统维护' } } } ] }) ]4、开始整合整合时会涉及一些资源文件引用的改造,一般改动会比较多,为不影响主分支开发,建议单独开个分支处理。整合过程比较繁琐,就不一一罗列了,主要涉及如下事项:替换 ENV 变量:vue-cli 工程的环境变量都是以Vue_APP 开头的变量,可以全局替换为VITE_APP开头,而且 Webpack 工程一般使用 process.env.XXX 访问ENV变量,而 Vite 是使用 import.meta.env.XXX 访问 ENV 变量,为了少改动,可以使用如下方式继续使用process.env.XXX 方式访问:define: { 'process.env': { ...env } }调整资源引用问题:Webpack 项目多数使用require('xxx')的方式在 JS 里引入资源,确实特别的方便,但是这一套在 Vite支持不是很好,即便有插件支持(如vite-plugin-commonjs)但依然没有 Webpack 的 require 机制强大。一些不需要参与打包的资源可以放在 public 目录,然后使用URL路径访问,参与打包的文件,可改为 import XXX from 'xxxx.png'的方式引入整体改造过程提交记录截图如下:优化整合后发现打包的文件比原 Webpack 打包的 dist 目录更大,前端工程层面的优化,主要以从以下几点着手处理:压缩:可采用vite-plugin-compression插件对打包后的文件进行了压缩处理静态资源上CDN:优化过程主要使用rollup-plugin-visualizer 对打包后的文件进行分析,对一些占用打包空间较大的文件使用CDN引入的方式进行优化按需引入:如lodash、moment.js等,尽量按需引入,减少非必须的语言包等,能很大程度的减小打包体积。附上本次改造核心的完整 vite.config.js 文件内容:import path from 'path' import vitePluginCommonjs from 'vite-plugin-commonjs' import { createHtmlPlugin } from 'vite-plugin-html' import { visualizer } from 'rollup-plugin-visualizer' import viteCompression from 'vite-plugin-compression' import externalGlobals from 'rollup-plugin-external-globals' // import eslintPlugin from 'vite-plugin-eslint' import { defineConfig, loadEnv } from 'vite' import legacy from '@vitejs/plugin-legacy' import vue2 from '@vitejs/plugin-vue2' export default ({ mode }) => { const env = loadEnv(mode, process.cwd()) const distMode = env.VITE_APP_DIST_MODE || 'paratera' const cdn = { scripts: mode.includes('development') ? [] : [ 'https://statics.paratera.com/console/libs/vue.2.7.14.min.js', // 'https://cdn.jsdelivr.net/npm/vue-router@3.6.5/dist/vue-router.min.js', // 'https://cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js', 'https://statics.paratera.com/console/libs/element-ui.2.15.13.min.js', 'https://statics.paratera.com/console/libs/exceljs.4.3.0.min.js' ] } // https://vitejs.dev/config/ const viteConfig = { base: '/', publicDir: `./public/${distMode}`, define: { 'process.env': { ...env } }, plugins: [ vue2(), // eslintPlugin(), createHtmlPlugin({ minify: true, pages: [ { entry: '/src/main.js', filename: 'index.html', template: 'index.html', injectOptions: { data: { title: '超算云服务控制台', cdn }, tags: [ { injectTo: 'body-prepend', tag: 'div', attrs: { id: 'tag1' } } ] } }, { entry: 'src/pages/admin/index.js', filename: 'admin.html', template: 'admin.html', injectOptions: { data: { title: '超算运服务控制台-系统维护', cdn // injectScript: `<script src="./inject.js"></script>`, } } } ] }), vitePluginCommonjs(), legacy({ targets: ['ie >= 11'], additionalLegacyPolyfills: ['regenerator-runtime/runtime'] }), viteCompression({ verbose: true, // 是否在控制台输出压缩结果 disable: false, // 是否禁用,相当于开关在这里 threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b,1b=8B, 1B=1024KB 那我们这里相当于 9kb多吧,就会压缩 algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw'] ext: '.gz' // 文件后缀 }) ], resolve: { extensions: ['.js', '.vue', '.json'], alias: [ { find: /@\/.+/, replacement: (val) => { return val.replace(/^@/, path.resolve(__dirname, './src/')) } }, { // this is required for the SCSS modules find: /^~(.*)$/, replacement: '$1' } ] }, css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/themes/${distMode}.scss";` } } }, server: { port: 8686 }, build: { // target: 'es2015', // cssTarget: 'chrome80', // brotliSize: false, // chunkSizeWarningLimit: 2000, // commonjsOptions: { // // 改为 ture 后就会转化 require 语法 // transformMixedEsModules: true // }, rollupOptions: { input: { index: path.resolve(process.cwd(), 'index.html'), admin: path.resolve(process.cwd(), 'admin.html') }, external: [ // 'vue', 'element-ui', 'exceljs' ], plugins: [ externalGlobals({ vue: 'Vue', // 'vue-router': 'VueRouter', // vuex: 'Vuex', 'element-ui': 'ELEMENT', 'exceljs': 'ExcelJS' }) ], output: { chunkFileNames: 'assets/js/chunks/[name].[hash].js', assetFileNames: (chunkInfo) => { // 用后缀名称进行区别处理 // 处理其他资源文件名 e.g. css png 等 let subDir = 'images' const extName = path.extname(chunkInfo.name) if (['.css'].includes(extName)) { subDir = 'css' } if (['.woff', '.ttf'].includes(extName)) { subDir = 'fonts' } return `assets/${subDir}/[name].[hash].[ext]` }, // 入口文件名 entryFileNames: 'assets/js/[name].[hash].js' } } } } const lifecycle = process.env.npm_lifecycle_event if (lifecycle.includes(':report')) { viteConfig.plugins.push(visualizer({ open: true, brotliSize: true, gzipSize: true, filename: './dist/report.html' })) } return defineConfig(viteConfig) } 升级前后 package.json 比对:成效对比项优化前(Webpack)优化后(Vite)安装依赖66.60s17.2开发启动110228ms2536ms打包部署123.77s1m 57s对比截图安装依赖:Webpack ↓Vite ↓开发启动:Webpack ↓Vite ↓打包截图:Webpack ↓Vite ↓
2023年06月24日
46 阅读
0 评论
0 点赞
2023-06-10
随动帮助文档实现
背景介绍我们项目提供了在线的项目文档,便于用户在使用过程中有什么问题可以通过直接查文档解决,也在一定程度上减少了公司运营及客服成本。在实际使用过程中,用户反馈造作文档的使用不是很方便,基本上要打开新Tab页签,不能方便的一遍对照文档,一边操作。解决方案为解决以上问题,在参考阿里云及腾讯云相关产品的文档模式后,引入了随动帮助文档机制。该机制主要实现模式如下:1、在相关功能指定位置添加文档入口2、单击文档名称后,在页面左下角显示文档弹出层(一下简称「文档查看器」),主要是系统的大部分功能使用右侧展开的抽屉组件 (Drawer)进行显示。3、打开的文档查看器,可垂直方向最大,可上下左右四边及四个角落通过拖拽调整大小4、可拖拽待页面任意位置5、文档查看器不影响页面中其他组件的交互该功能是系统每个功能模块都有可能会用到的,所以将起提取为一个公共组件。组件只需传入功能标识,然后调用内容管理系统的接口获取数据,如果有数据则显示文档入口,由于一个功能极有可能会对应有多篇帮助文档,用户从文档入口单击将展开文档列表,用户单击需要查看的文档,则在左下角显示文档。实现过程示例中均使用模拟的数据,实际数据在生产环境调用内容管理系统的接口获取。实现文档查看器组件HelpModal.vue文档查看器的拖拽及大小调整均通过指令控制<template> <el-dialog ref="dlg" v-dragAndResize :modal="false" top="auto" :visible.sync="isShow" custom-class="help-modal-wrap" width="400px" :close-on-click-modal="false" append-to-body > <div slot="title" class="dialog-title"> <h3>功能指南</h3> <div class="panel-actions"> <em class="panel-action console-svg svg-vertical-expand margin-right-xs" :class="{ 'svg-vertical-expand': !verticalMaxed, 'svg-vertical-collapse': verticalMaxed }" :title="verticalMaxed ? '还原' :'垂直最大化'" @click="setDlgMaxHeight" /> </div> </div> <div class="dlg-body-inner"> <div class="content-wrap"> <h2 class="article-title"> 如何使用一键转区 </h2> <div class="article-subtitle"> <span class="article-time">更新时间:2022-08-09 10:10</span> </div> <div class="article-content"> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 可使用【一键转区】功能,将超算账号的数据转移至指定的超算上进行使用,如超算中心A3分区的sc0001账号(即为源目标)转移至超算分区A6的sc0001(即为目的目标),具体操作如下:</span> </p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 1. 单击【一键转区】中的【申请一键转区】</span> </p> <p> <span style="font-size:14px"><img src="https://kbs.paratera.com/uploads/1/image/public/202208/20220809100845_a3269g5dky.png" title="" alt="image.png" ></span> </p> <p> <span style="font-size:14px"><img src="https://kbs.paratera.com/uploads/1/image/public/202208/20220809100855_atmxepobl8.png" title="" alt="image.png" ></span> </p> <p><span style="font-size:14px"> </span></p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:14px"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 2. 在【源目标】处选择可用的超算分区及超算账号,例如A3超算分区的sc0001;</span></span> </p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 在【目的目标】处选择匹配的超算分区,不用选择转区后的超算账号,例如A6超算分区;</span> </p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 在【源目标账号环境信息】处填软件使用时的环境要求。</span> </p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 3. 单击【确定】,会有提示框告知源目标超算账号保存3个月,3个月后超算账号回收的提醒。</span> </p> <p style="line-height:normal"> <span style="font-family:'arial' , 'helvetica' , sans-serif;font-size:16px"> 4. 在【一键转区】的页面查看工单进展。</span> </p> </div> </div> <div class="outer-link-wrap"> <el-button type="primary" class="btn-open-in-window" @click="openDocLink"> 新窗口查看文档 </el-button> </div> </div> </el-dialog> </template> <script> import { setStyle, on, off, addClass } from 'element-ui/lib/utils/dom' export default { name: 'HelpModal', directives: { dragAndResize: { bind(el, binding, vnode, oldVnode) { addClass(el, 'dlg-wrap-help') // 弹框可拉伸最小宽高 const minWidth = 300 const minHeight = 300 // 获取弹框头部(这部分可双击全屏) const dialogHeaderEl = el.querySelector('.el-dialog__header') // 弹窗 const dragDom = el.querySelector('.el-dialog') dragDom.className = dragDom.className + ' dialog-can-resize' // // 给弹窗加上overflow auto;不然缩小时框内的标签可能超出dialog; // dragDom.style.overflow = 'auto' // 清除选择头部文字效果 dialogHeaderEl.onselectstart = new Function('return false') // 头部加上可拖动cursor dialogHeaderEl.style.cursor = 'move' // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) // 初始设置在页面左下角 const initialHeight = window.innerHeight * 2 / 3 setStyle(dragDom, 'height', initialHeight + 'px') setStyle(dragDom, 'top', window.innerHeight * 1 / 3 - 10 + 'px') const moveDown = (e) => { e.preventDefault() // 鼠标按下,计算当前元素距离可视区的距离 const disX = e.clientX - dialogHeaderEl.offsetLeft const disY = e.clientY - dialogHeaderEl.offsetTop // 获取到的值带px 正则匹配替换 let styL, styT // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px if (sty.left.includes('%')) { styL = +document.body.clientWidth * (+sty.left.replace(/%/g, '') / 100) styT = +document.body.clientHeight * (+sty.top.replace(/%/g, '') / 100) } else { styL = +sty.left.replace(/px/g, '') styT = +sty.top.replace(/px/g, '') } document.onmousemove = function(e) { e.preventDefault() // 通过事件委托,计算移动的距离 const l = e.clientX - disX const t = e.clientY - disY // 移动当前元素 dragDom.style.left = `${l + styL}px` dragDom.style.top = `${t + styT}px` // 将此时的位置传出去 // binding.value({x:e.pageX,y:e.pageY}) } document.onmouseup = function(e) { document.onmousemove = null document.onmouseup = null } } on(dialogHeaderEl, 'mousedown', moveDown) // 添加位置调整锚点 const buildResizeElements = (dragDom) => { const directions = ['top', 'right', 'bottom', 'left', 'top-right', 'top-left', 'bottom-right', 'bottom-left'] for (const direction of directions) { const resizeEl = document.createElement('div') resizeEl.className = `resize-handle resize-${direction}` resizeEl.setAttribute('resize', direction) dragDom.appendChild(resizeEl) } } buildResizeElements(dragDom) let moveOpts = null on(document, 'mousedown', (event) => { if (event.which !== 1 && event.type === 'mousedown') { return } const { target } = event if (!target.className.includes('resize-handle')) { return } const resizeDirection = target.getAttribute('resize') moveOpts = { resizeDirection: resizeDirection, clientX: event.clientX, clientY: event.clientY, startWidth: dragDom.offsetWidth, startHeight: dragDom.offsetHeight, offsetLeft: dragDom.offsetLeft, offsetTop: dragDom.offsetTop } const screenWidth = window.innerWidth const screenHeight = window.innerHeight const mouseMoveHandler = (event) => { event.preventDefault() const style = dragDom.style const resizeDirection = moveOpts.resizeDirection const { startWidth, startHeight } = moveOpts let left = moveOpts.offsetLeft let top = moveOpts.offsetTop let width = moveOpts.startWidth let height = moveOpts.startHeight let clientX = event.clientX let clientY = event.clientY let x = (clientX = screenWidth <= clientX ? screenWidth : clientX) <= 0 ? 0 : clientX let y = (clientY = screenHeight <= clientY ? screenHeight : clientY) <= 0 ? 0 : clientY x = event.clientX - moveOpts.clientX y = event.clientY - moveOpts.clientY switch (resizeDirection) { case 'top': top = y + top height = -y + height break case 'right': width = x + width break case 'bottom': height = y + height break case 'left': left = x + left width = -x + width break case 'top-left': left = x + left top = y + top width = -x + width height = -y + height break case 'top-right': top = y + top width = x + width height = -y + height break case 'bottom-right': width = x + startWidth height = y + startHeight break case 'bottom-left': left = x + left width = -x + startWidth height = y + startHeight } left = left <= 0 ? 0 : left top = top <= 0 ? 0 : top style.left = left + 'px' style.top = top + 'px' style.width = Math.max(minWidth, width) + 'px' style.height = Math.max(minHeight, height) + 'px' } const mouseUpHandler = (event) => { console.error('mouseUp', event) off(document, 'mousemove', mouseMoveHandler) off(document, 'mouseup', mouseUpHandler) } on(document, 'mousemove', mouseMoveHandler) on(document, 'mouseup', mouseUpHandler) }) } } }, props: { show: { type: Boolean, required: true, default: false }, docId: { type: Number, required: true } }, data() { return { originStyle: null, verticalMaxed: false } }, computed: { isShow: { get() { return this.show }, set(val) { this.$emit('update:show', val) } } }, created() { this.init() }, methods: { async init() { }, setDlgMaxHeight() { const $dlg = this.$refs['dlg'] console.log('dlg', $dlg) const dlgEl = $dlg.$el.childNodes[0] const dlgStyle = dlgEl.style if (this.verticalMaxed) { setStyle(dlgEl, 'top', this.originStyle.top) setStyle(dlgEl, 'height', this.originStyle.height) } else { this.originStyle = { top: dlgStyle.top, height: dlgStyle.height } setStyle(dlgEl, 'top', 0) setStyle(dlgEl, 'height', window.innerHeight + 'px') } this.verticalMaxed = !this.verticalMaxed }, openDocLink() { window.open('https://kbs.paratera.com/info/1272') } } } </script> <style lang="scss"> @import "./modal-resize.scss"; .help-modal-wrap { border-radius: 0; pointer-events: all; margin: 0; left: 10px; top: calc(100vh - 100% - 10px); // transition: all ease 0.2s; .el-dialog__header { padding: 10px; background-image: url(./header-bg-2.jpg); background-repeat: no-repeat; background-position: 100% 0%; background-size:cover; background-color: $theme-light-1; color:#fff; position: relative; em.console-svg { font-size: 16px; cursor: pointer } .el-dialog__headerbtn { top: 15px; right: 15px; } .el-dialog__close { color:#fff; } } .el-dialog__body { flex: auto; overflow: auto; height: 100%; padding: 0; } .dlg-body-inner { box-sizing: border-box; height: 100%; // position: relative; // overflow: auto; // min-height: 352px; // max-height: calc(100vh - 60px); } } </style> <style lang="scss" scoped> @import "../../dashboard/modals/ArticleContent.scss"; :deep(.dlg-wrap-help) { pointer-events: none; } .dialog-title { h3 { font-size: 16px; font-weight:normal; } } .panel-actions { position: absolute; right: 30px; top: 10px; } .panel-action { width: 24px; height:24px; line-height: 24px; text-align: center; display:inline-block; margin: 0 3px; cursor: pointer; &:hover { background-color:rgba(0,0,0,0.15) } } .content-wrap { height: calc(100% - 95px); overflow: auto; padding: 10px; } article { padding-top: 20px; } .article-title { text-align: left; color: #333333; padding-top: 10px; font-size: 22px; font-weight: normal; } .article-subtitle { position: relative; width: 100%; padding-top: 10px; text-align: left; color: #999; font-size: 14px; } .article-content { padding: 10px; } .outer-link-wrap { padding: 10px; position: static; background-color: #fff; width: 100%; bottom: 0; } .btn-open-in-window { display: block; width: 100%; box-sizing: border-box; margin: 0 auto; } </style> <style> .dlg-wrap-help { pointer-events: none; z-index: 9000!important; } </style> modal-resize.scss.dialog-can-resize .resize-handle { position: absolute; z-index: 100; display: block } .dialog-can-resize .resize-top { cursor: n-resize; top: -3px; left: 0px; height: 7px; width: 100% } .dialog-can-resize .resize-bottom { cursor: s-resize; bottom: -3px; left: 0px; height: 7px; width: 100% } .dialog-can-resize .resize-right { cursor: e-resize; right: -3px; top: 0px; width: 7px; height: 100% } .dialog-can-resize .resize-left { cursor: w-resize; left: -3px; top: 0px; width: 7px; height: 100% } .dialog-can-resize .resize-bottom-right { cursor: se-resize; width: 20px; height: 20px; right: -8px; bottom: -8px; background: url('./resize_corner.png') no-repeat; opacity: .4; filter: alpha(opacity=40) } .dialog-can-resize .resize-bottom-left { cursor: sw-resize; width: 16px; height: 16px; left: -8px; bottom: -8px } .dialog-can-resize .resize-top-left { cursor: nw-resize; width: 16px; height: 16px; left: -8px; top: -8px } .dialog-can-resize .resize-top-right { cursor: ne-resize; width: 16px; height: 16px; right: -8px; top: -8px } .dialog-can-resize .aui-min,.dialog-can-resize .aui-max { display: block } .dialog-can-resize .resize-top-right:before,.dialog-can-resize .resize-bottom-right:before,.dialog-can-resize .resize-bottom-left:before,.dialog-can-resize .resize-top-left:before { position: absolute; content: ""; -ms-transition: all .2s; -webkit-transition: all .2s; -moz-transition: all .2s; -o-transition: all .2s; transition: all .2s; width: 15px; height: 15px; opacity: 0; pointer-events: none; border: 1px solid #1890ff; border-radius: 0 } .dialog-can-resize .resize-top-right:hover:before,.dialog-can-resize .resize-bottom-right:hover:before,.dialog-can-resize .resize-bottom-left:hover:before,.dialog-can-resize .resize-top-left:hover:before,.dialog-can-resize .resize-top-right:active:before,.dialog-can-resize .resize-bottom-right:active:before,.dialog-can-resize .resize-bottom-left:active:before,.dialog-can-resize .resize-top-left:active:before { opacity: 1 } .dialog-can-resize .resize-top-right:before { border-left: none; border-bottom: none; border-radius: 0 4px 0 0; left: -8px; top: 8px } .dialog-can-resize .resize-bottom-right:before { border-left: none; border-top: none; border-radius: 0 0 4px 0; left: -4px; top: -4px } .dialog-can-resize .resize-bottom-left:before { border-right: none; border-top: none; border-radius: 0 0 0 4px; left: 8px; top: -8px } .dialog-can-resize .resize-top-left:before { border-right: none; border-bottom: none; border-radius: 4px 0 0 0; left: 8px; top: 8px } 实现文档入口组件<template> <div v-if="docList.length > 0" class="fn-doc-wrap"> <el-popover position="bottom" trigger="hover" > <el-button slot="reference" type="text" class="padding-xs"> <em class="console-svg svg-fn-help" /> </el-button> <ul class="doc-list"> <li v-for="docItem in docList" :key="docItem.id" @click="showHelpModal(docItem.id)"> <p v-text="docItem.title" /><svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-tool-arrow" data-v-68bcb17a=""><path d="M13.7031 11.5259L13.7031 4.77588L6.95314 4.77589M4.30093 14.177L13.3949 5.08301" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg> </li> </ul> </el-popover> </div> </template> <script> export default { name: 'ParaFnDoc', desc: '随动帮助文档入口组件,使用过程中只需要传入关键词即可', props: { keyword: { type: String, required: true } }, data() { return { docList: [] } }, created() { this.init() }, methods: { init() { // TODO 根据关键词获取文档列表 this.docList = [ { id: 1, title: '一键转区功能说明' }, { id: 2, title: '为什么一键转区功能不可用?' }, { id: 3, title: '为什么一键转区提交后没有及时生效?' } ] }, showHelpModal(docId) { window.ParaCommon.showHelpModal(docId) } } } </script> <style lang="scss" scoped> .fn-doc-wrap { display: inline-block; vertical-align: middle; } .doc-list li { line-height: 22px; margin-bottom: 5px; padding: 7px 10px; list-style:none; display: flex; align-items: center; justify-content: center; &:hover { background-color: #f7f8fa; cursor: pointer; .icon-tool-arrow { stroke: #1e80ff; } } p { flex: 1 } .link-arrow { color:blue } } </style> 实现后的效果{dplayer src="/usr/uploads/2023/06/592593977.mp4"/}
2023年06月10日
16 阅读
0 评论
0 点赞
2023-06-06
Ubuntu 添加FRP客户端自启动
背景介绍今年618 的时候购置了一台迷你主机,主要用于家庭内部服务器使用,主要基于Docker 部署其他应用,而 Ubuntu 作为 docker 原生支持最好的操作系统,而且还有漂亮的桌面,当然也就成了本迷你主机的操作系统首选。装完系统后,部署的很多应用只能内部使用,为了方便,当然不能只局限于家庭内部环境使用。结合之前不熟的FRP服务端,完全可以对外提供WEB服务,于是便有了本期的教程。下载 FRPFRP 是服务端和客户端打包在一个压缩包文件里的,可以直接从github下载就好。FRP 发布地址:https://github.com/fatedier/frp/releases本处直接下载最新的0.49.0 版本,根据操作系统,本处选择frp_0.49.0_linux_amd64.tar.gz 进行下载:wget https://github.com/fatedier/frp/releases/download/v0.49.0/frp_0.49.0_linux_amd64.tar.gz下载后 解压文件tar -zxvf frp_0.49.0_linux_amd64.tar.gz将解压的文件移动到当前用户有权限的目录,本案例中移动到 /data/apps/frp目录编辑 frpc.ini 文件本处结合实际情况,修改内容如下,部分涉密数据做了调整:[common] server_addr = SERVER_IP server_port = 7000 # for authentication token = TOKEN log_file = /data/apps/frp/frpc.log log_level = info log_max_days = 30 [home_ssh] type = tcp local_ip = 127.0.0.1 local_port = 22 remote_port = 4000 [home_web_pan] type = http local_ip = 127.0.0.1 local_port = 80 http_user = zhuzl http_pwd = PASSWORD subdomain = pan [home_web_kod] type = http local_ip = 127.0.0.1 local_port = 80 http_user = zhuzl http_pwd = PASSWORD subdomain = kod 配置完成后,可直接运行frpc 验证是否OK../frpc若有问题,可检查frpc.ini相关配置信息是否正确配置frpc自启动配置自启动过程中,为避免权限相关问题,本处直接切换为 root 账号添加 frpc.servicevi /etc/systemd/system/frpc.service输入如下服务配置内容[Unit] Description=Frp Client Service After=network.target [Service] Type=simple User=jiuzilong Restart=on-failure RestartSec=5s ExecStart=/data/apps/frp/frpc -c /data/apps/frp/frpc.ini [Install] WantedBy=multi-user.target 启用服务# 启用服务 systemctl enable frpc.service # 禁用服务 systemctl disable frpc.service重启服务systemctl daemon-reload systemctl start frpc验证服务启动状态systemctl status frpc参考链接FRP官方文档: https://gofrp.org/docs/overview/FRP服务端安装并设置开机自启动: https://blog.zhuzhilong.cn/software/install-frps-as-service.html推荐另一种docker compose 的启动方式:version: '3.3' services: frpc: restart: always network_mode: host volumes: - './frpc.ini:/etc/frp/frpc.ini' container_name: frpc image: snowdreamtech/frpc
2023年06月06日
33 阅读
0 评论
0 点赞
2023-06-05
部署Cloudreve
参考链接:https://docs.cloudreve.org/getting-started/install#docker-composedocker-compose.ymlversion: "3.8" services: cloudreve: container_name: cloudreve image: cloudreve/cloudreve:latest restart: unless-stopped ports: - "8004:5212" volumes: - temp_data:/data - ./cloudreve/uploads:/cloudreve/uploads - ./cloudreve/conf.ini:/cloudreve/conf.ini - ./cloudreve/cloudreve.db:/cloudreve/cloudreve.db - ./avatar:/cloudreve/avatar depends_on: - aria2 aria2: container_name: aria2 image: ddsderek/aria2-pro restart: unless-stopped environment: - RPC_SECRET=your_aria_rpc_token - RPC_PORT=6800 - DOWNLOAD_DIR=/data - PUID=1000 - PGID=1000 - UMASK_SET=022 - TZ=Asia/Shanghai volumes: - ./aria2/config:/config - temp_data:/data volumes: temp_data: driver: local driver_opts: type: none device: ./data o: bind 在当前目录创建相关目录及文件mkdir -vp cloudreve/{uploads,avatar} \ && touch cloudreve/conf.ini \ && touch cloudreve/cloudreve.db \ && mkdir -p aria2/config \ && mkdir -p data/aria2 \ && chmod -R 777 data/aria2启动容器docker compose up -d启动后从日志中查看登录账号信息:
2023年06月05日
13 阅读
0 评论
0 点赞
2023-06-05
部署spug
参考: https://www.spug.cc/docs/install-docker创建 docker-compose.ymlversion: '3.9' services: spug: image: openspug/spug-service container_name: spug privileged: true restart: always volumes: - ./service:/data/spug - ./repos:/data/repos ports: - 8002:80 environment: - MYSQL_DATABASE=spug - MYSQL_USER=spug - MYSQL_PASSWORD=Passw0rd - MYSQL_HOST=192.168.1.200 - MYSQL_PORT=3306启动容器docker compose up -d初始化以下操作会创建一个用户名为 zhuzl 密码为 Passw0rd 的管理员账户,可自行替换管理员账户/密码。docker exec spug init_spug zhuzl Passw0rd
2023年06月05日
17 阅读
0 评论
0 点赞
2023-05-30
项目原型工程搭建实践分享:前端数据mock
背景介绍大部分项目有产品经理参与前期的需求及原型制作,但近两年我们组负责的项目没有专职产品经理及UI工程师参与。由于项目已上线正式交付给用户使用,很多新需求不便于直接在ui工程大刀阔斧的改动,毕竟从需求 → 原型 → 前端UI → 后端接口 → 前后端联调 → 正式上线,一般都会有一个不短的周期。直接在前端ui工程跟进需求的话,中间极有可能穿插其他的bug修复及功能调整相关的短期改动需求,导致原型相关的代码跟ui代码混在一起,难免会由于粗心造成一些可大可小的线上事故。项目分析针对以上实际情况,最终项目组决定由前端工程师来负责项目的原型工作,原型效果由需求方确认后再着手后续的前后端开发工作。本着节约工作量考虑,原型工程使用 ui工程相同的代码,那样后期在原型工程的改动也可以很方便的迁移到ui工程。为达到这个目的,需要将在原型工程请求的后端数据改用 mock 方式。mock 数据有多种可行的实现方式:大部分API管理应用(如Torna、YApi、apizza等)都在提供管理接口的核心功能上带有给接口提供mock数据机制,然后开发时代理到API管理应用的mock路径本地维护一套mock数据,然后起一个mock服务提供接口请求本地维护一套接口json数据,拦截 ajax 请求响应 mock 的数据以上实现方式都有各自的优缺点:方案1:受制于接口管理平台,大部分接口管理平台不能自定义接口path,那样就会造成mock,路径跟实际路径不一致。而且在不同的平台维护,极容易造成最终的数据不一致的情况。方案2:可使用Node.js自写一套解析json提供mock服务的程序,该方式能最大程度的还原浏览器网络请求过程,要花更多的时间定义json 数据;由于实际使用时存在路径变量的情况,复杂的路由路径跟 mock数据的匹配方式也要花不少时间。若有足够多的时间处理这个路径匹配机制,该方案是最佳,整个前端工程的代码不用调整(那样也更更方便的跟ui工程做代码比对及合并),只需在原型工程代理一个不同的服务路径即可。方案3:有成熟的技术实现方案,能最快的响应现有需求,但需在已有 ui 工程上调整部分网络请求相关的文件。最终我在实现过程中是选择了第三种,主要是原型工程都是前端工程师维护,相关的mock数据也都在一个平台直接管理是最节省时间的。实现方案为实现该方案,在原有 ui 工程的基础上,主要做了开展了如下工作:1、模拟用户认证,添加 src/core/mock.js 文件,可根据系统实际情况模拟数据即可,核心内容如下:/** * 原型工程为跟console-ui整体结构保持一致,为跳过用户认证逻辑,本处添加一些环境初始化相关的数据 */ export function mockInit() { // 模拟token 进行 sessionStorage.setItem('token', 'p_RQAnFyIFZtGDS_iZwThGs5z8tWOM0BDSN03z1EWkA.Q0Ayf2dx4aaFfoJzLfS3YiRoqdScvk-OHJn7Kb_lRQU') // 模拟用户信息 sessionStorage.setItem('userid', 'SELF-DAN4_QiFBwU7CF-AQkPTatwW6tjKV_FjyDDUpkGYSvA') sessionStorage.setItem('para-token', 'TmdOGodfh75ueoAZsaeznT5qpeZJ_eaAyWJCwcao8Ro-2082234049') sessionStorage.setItem('userLoginLogId', '2744') } export const mockFormResponse = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(null) }, 1000) }) } 2、main.js 引入并执行初始化方法:import { mockInit } from './core/mock' mockInit() 3、 添加 src/api/mock 目录,然后添加mockData子目录用于管理相关的 mock 数据,基本的目录结构如下图所示:4、 添加 src/api/mock/index.js 主要用于获取所有 mockData 目录下以 .json结尾的文件,并提供 mock数据,核心代码如下:/** * 用于将 mockData 目录下的所有以.json 文件结尾的mock文件整合为一个,对外提供mock服务 */ import lodashGet from 'lodash/get' import { mockFormResponse } from '../../core/mock.js' const modulesFiles = require.context('./mockData', false, /\.json$/) const mockDataObj = {} modulesFiles.keys().map(modulePath => { const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') mockDataObj[moduleName] = modulesFiles(modulePath) }) export default { /** * * @param {String} mockPath mock数据路径,一般是「模块名.mock数据对象名」 * @param {Number} timeout 延时时长,单位为ms,默认为1秒 */ getMockData: (mockPath, timeout) => { if (!timeout) { timeout = 1000 } if (mockPath === 'common.formMock') { return mockFormResponse() } const mockData = lodashGet(mockDataObj, mockPath) return new Promise((resolve, reject) => { setTimeout(() => { if (mockData) { resolve(mockData) } else { reject(new Error('数据暂未定义')) } }, timeout) }) }, mockFormResponse }5、改造 Ajax 请求逻辑.本系统使用的 axios 作为 http 库,并且使用层面做了全局封装。而 axios 提供了 adapter 机制可以很方便的实现我们的 mock 场景,核心代码如下:import axios from 'axios' import mockService from '../api/mock/index.js' const service = axios.create({ baseURL: process.env.VUE_APP_GATEWAY_URL, // url = base url + request url adapter: async(config) => { // mock数据拦截 if (config.mockCode) { const data = await mockService.getMockData(config.mockCode) return new Promise((resolve, reject) => { resolve({ data, status: 200 }) }) } }, // withCredentials: true, send cookies when cross-domain requests timeout: 60000 // request timeout })6、改造所有 api 目录下所有获取数据的方法,在原有基础上统一添加 mockCode 参数。示例如下:api 请求方法种添加 mockCode:对应模块的.json mock 文件种添加需响应的数据:7、改造表格封装组件。为便于表格数据的异步加载,我们基于 el-table 做了些封装,额可以传入一个url 就可方便的分页获取表格数据,这一套机制是不走我们api 目录封装的方法的,故需要在请求时也需要指定 mockCode,具体示例代码如下图所示:为适配该场景,在发起请求前如果存在mockCode,则在请求参数种添加mockCode:总结通过该工作,扩展了解决问题的多角度思维模式,面对需求只要前期做好规划,梳理出可行的实现方案,最后的技术实现往往是最简单的。
2023年05月30日
18 阅读
0 评论
0 点赞
2023-05-28
windows 安装nvm 及基本使用
近些年 Node.js 的版本升级比较快,而我们很多项目维护周期也会维持很多年,而维护一些老项目的时候,在一些新版本 Node.js上会报一大堆的错,为了尽快修复 bug,没必要花太多的时间升级一大堆的依赖,而且贸然升级,对于线上在用的项目也存在诸多不确定性方面的隐患。对于前端开发者而言,选择一款方便的多 Node.js 版本控制工具,俨然是非常有必要的。而nvm 是一款可以用于管理多版本 Node.js 的工具,可以根据实际情况,轻松切换项目所需的node 版本,在开发过程中非常便捷,不用手工卸载原版本再下载安装新Node版本。下载github发布页:https://github.com/coreybutler/nvm-windows/releases我选择nvm-setup.exe文件进行下载,也有用于免安装的绿色版文件(nvm-noinstall.zip),那样得下载后解压并手工设置环境变量。安装下载后是一个nvm-setup.exe,安装过程一路 Next即可:安装完成后,打开cmd命令行,输入 nvm version 能正常显示类似下图所示版本信息即可:使用查看可安装版本:nvm list availableD:\GitRoot\SE\dmc\magic-boot>nvm list available | CURRENT | LTS | OLD STABLE | OLD UNSTABLE | |--------------|--------------|--------------|--------------| | 21.6.2 | 20.11.1 | 0.12.18 | 0.11.16 | | 21.6.1 | 20.11.0 | 0.12.17 | 0.11.15 | | 21.6.0 | 20.10.0 | 0.12.16 | 0.11.14 | | 21.5.0 | 20.9.0 | 0.12.15 | 0.11.13 | | 21.4.0 | 18.19.1 | 0.12.14 | 0.11.12 | | 21.3.0 | 18.19.0 | 0.12.13 | 0.11.11 | | 21.2.0 | 18.18.2 | 0.12.12 | 0.11.10 | | 21.1.0 | 18.18.1 | 0.12.11 | 0.11.9 | | 21.0.0 | 18.18.0 | 0.12.10 | 0.11.8 | | 20.8.1 | 18.17.1 | 0.12.9 | 0.11.7 | | 20.8.0 | 18.17.0 | 0.12.8 | 0.11.6 | | 20.7.0 | 18.16.1 | 0.12.7 | 0.11.5 | | 20.6.1 | 18.16.0 | 0.12.6 | 0.11.4 | | 20.6.0 | 18.15.0 | 0.12.5 | 0.11.3 | | 20.5.1 | 18.14.2 | 0.12.4 | 0.11.2 | | 20.5.0 | 18.14.1 | 0.12.3 | 0.11.1 | | 20.4.0 | 18.14.0 | 0.12.2 | 0.11.0 | | 20.3.1 | 18.13.0 | 0.12.1 | 0.9.12 | | 20.3.0 | 18.12.1 | 0.12.0 | 0.9.11 | | 20.2.0 | 18.12.0 | 0.10.48 | 0.9.10 | This is a partial list. For a complete list, visit https://nodejs.org/en/download/releases安装指定版本Node:nvm install 版本号D:\GitRoot\SE\dmc\magic-boot>nvm install 21 Downloading node.js version 21.6.2 (64-bit)... Extracting node and npm... Complete npm v10.2.4 installed successfully. Installation complete. If you want to use this version, type nvm use 21.6.2查看已安装版本:nvm listD:\GitRoot\SE\dmc\magic-boot>nvm list 21.6.2 * 20.10.0 (Currently using 64-bit executable) 更多使用方法可以输入 nvm --help 命令可查看:D:\GitRoot\SE\dmc\magic-boot>nvm --help Running version 1.1.12. Usage: nvm arch : Show if node is running in 32 or 64 bit mode. nvm current : Display active version. nvm debug : Check the NVM4W process for known problems (troubleshooter). nvm install <version> [arch] : The version can be a specific version, "latest" for the latest current version, or "lts" for the most recent LTS version. Optionally specify whether to install the 32 or 64 bit version (defaults to system arch). Set [arch] to "all" to install 32 AND 64 bit versions. Add --insecure to the end of this command to bypass SSL validation of the remote download server. nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls. nvm on : Enable node.js version management. nvm off : Disable node.js version management. nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy. Set [url] to "none" to remove the proxy. nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url. nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/cli/archive/. Leave [url] blank to default url. nvm uninstall <version> : The version must be a specific version. nvm use [version] [arch] : Switch to use the specified version. Optionally use "latest", "lts", or "newest". "newest" is the latest installed version. Optionally specify 32/64bit architecture. nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode. nvm root [path] : Set the directory where nvm should store different versions of node.js. If <path> is not set, the current root will be displayed. nvm [--]version : Displays the current running version of nvm for Windows. Aliased as v.
2023年05月28日
16 阅读
0 评论
0 点赞
2023-05-24
记一次使用v-charts绘制图表出现重影的处理经历分享
背景介绍目前维护的项目属于多年积累的老项目了,使用的 vue2 + vue-router + vuex + element-ui + webpack 的技术架构。涉及图表相关的库主要使用 echarts@4.9.0 和 饿了么前端团队基于 echarts 4.x 封装的 v-charts 1.19.0。近期接运营要求,需要增加一些图表效果,基本界面效果实现如下:问题但在实现过程中,在区域缩放组件拖动调整数据显示范围时,发现x轴会出现重影的现象,如下图所示:排查过程初期怀疑跟图表配置项的 xAxis 相关配置信息有关,随将 position: 'bottom' 配置项注释掉,发现重影现象没有了,但是上下出现了重复的x 轴label如下图所示:将echarts 图表配置信息拷贝到 echarts 实例页面(https://echarts.apache.org/v4/examples/zh/editor.html?c=bar-large)进行验证,发现在该页面不存在重影现象:配置信息如下:var option = { "color": ["#004CA1", "#32D4E3", "#03A2DC", "#B581E3", "#0F7DFA", "#c4b4e4"], "tooltip": { "trigger": "axis", "axisPointer": { "type": "shadow" } }, "legend": { "show": true, "top": -5, "data": ["CPU", "GPU"] }, "grid": { "right": "5%", "top": "10%", "bottom": "20%", "containLabel": true }, "dataZoom": [ { "type": "inside" }, { "type": "slider", "left": 77, "right": 77 } ], "xAxis": { "data": [ "2022-11-26", "2022-11-27", "2022-11-28", "2022-11-29", "2022-11-30", "2022-12-01", "2022-12-02", "2022-12-03", "2022-12-04", "2022-12-05", "2022-12-06", "2022-12-07", "2022-12-08", "2022-12-09", "2022-12-10", "2022-12-11", "2022-12-12", "2022-12-13", "2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17", "2022-12-18", "2022-12-19", "2022-12-20", "2022-12-21", "2022-12-22", "2022-12-23", "2022-12-24", "2022-12-25", "2022-12-26", "2022-12-27", "2022-12-28", "2022-12-29", "2022-12-30", "2022-12-31", "2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07", "2023-01-08", "2023-01-09", "2023-01-10", "2023-01-11", "2023-01-12", "2023-01-13", "2023-01-14", "2023-01-15", "2023-01-16", "2023-01-17", "2023-01-18", "2023-01-19", "2023-01-20", "2023-01-21", "2023-01-22", "2023-01-23", "2023-01-24", "2023-01-25", "2023-01-26", "2023-01-27", "2023-01-28", "2023-01-29", "2023-01-30", "2023-01-31", "2023-02-01", "2023-02-02", "2023-02-03", "2023-02-04", "2023-02-05", "2023-02-06", "2023-02-07", "2023-02-08", "2023-02-09", "2023-02-10", "2023-02-11", "2023-02-12", "2023-02-13", "2023-02-14", "2023-02-15", "2023-02-16", "2023-02-17", "2023-02-18", "2023-02-19", "2023-02-20", "2023-02-21", "2023-02-22", "2023-02-23", "2023-02-24", "2023-02-25", "2023-02-26", "2023-02-27", "2023-02-28", "2023-03-01", "2023-03-02", "2023-03-03", "2023-03-04", "2023-03-05", "2023-03-06", "2023-03-07", "2023-03-08", "2023-03-09", "2023-03-10", "2023-03-11", "2023-03-12", "2023-03-13", "2023-03-14", "2023-03-15", "2023-03-16", "2023-03-17", "2023-03-18", "2023-03-19", "2023-03-20", "2023-03-21", "2023-03-22", "2023-03-23", "2023-03-24", "2023-03-25", "2023-03-26", "2023-03-27", "2023-03-28", "2023-03-29", "2023-03-30", "2023-03-31", "2023-04-01", "2023-04-02", "2023-04-03", "2023-04-04", "2023-04-05", "2023-04-06", "2023-04-07", "2023-04-08", "2023-04-09", "2023-04-10", "2023-04-11", "2023-04-12", "2023-04-13", "2023-04-14", "2023-04-15", "2023-04-16", "2023-04-17", "2023-04-18", "2023-04-19", "2023-04-20", "2023-04-21", "2023-04-22", "2023-04-23", "2023-04-24", "2023-04-25", "2023-04-26", "2023-04-27", "2023-04-28", "2023-04-29", "2023-04-30", "2023-05-01", "2023-05-02", "2023-05-03", "2023-05-04", "2023-05-05", "2023-05-06", "2023-05-07", "2023-05-08", "2023-05-09", "2023-05-10", "2023-05-11", "2023-05-12", "2023-05-13", "2023-05-14", "2023-05-15", "2023-05-16", "2023-05-17", "2023-05-18", "2023-05-19", "2023-05-20", "2023-05-21", "2023-05-22", "2023-05-23", "2023-05-24" ], "type": "category", "silent": false, "axisLine": { "show": true }, "axisLabel": { "show": true, "interval": "auto", "showMinLabel": true, "showMaxLabel": true }, "scale": true, "splitLine": { "show": false }, "splitArea": { "show": true } }, "yAxis": { "type": "value", "axisLine": { "show": true } }, "series": [ { "type": "bar", "stack": "总量", "name": "CPU", "barMaxWidth": 40, "label": { "show": false, "position": "insideRight" }, "large": true, "data": [ 41284.35, 47697.4, 8413.61, 9252.11, 31770.33, 35508.78, 46398.67, 58060.04, 18176.99, 36094.06, 9143.29, 29802.65, 3537.02, 47931.77, 45087.27, 16314.9, 56488.59, 34108.47, 32244.21, 13635.22, 58377.44, 462.67, 56279.27, 2491.55, 13690.14, 29416.47, 35530.21, 18119.02, 23853.13, 33763.83, 7201.61, 26889.05, 51124.77, 53054.58, 48729.74, 37691.94, 46411.41, 25579.66, 30987.66, 18372.65, 45733.25, 56040.3, 13739.55, 16133.89, 53678.09, 1180.71, 4276.45, 11854.02, 46574.02, 6939.56, 18079.92, 25200.51, 47832.78, 25691.29, 14248.72, 4032.22, 39072.21, 12722.16, 42049.24, 27181.52, 29552.25, 51946, 48863.94, 22122, 39071.33, 50454.55, 59850.52, 23004.45, 51904.63, 28075.69, 38243.83, 32189.07, 1613.23, 40114.69, 30133.03, 28253.31, 38028.55, 25430.73, 23515.73, 9958.14, 38543.8, 36322.66, 57980.19, 57456.79, 27707.03, 269.52, 7980.29, 17138.2, 432.99, 48010.12, 1241.83, 39925.79, 41015.29, 38714.92, 18084.32, 28493.39, 51526.46, 22794.2, 32825.65, 53792.55, 18065.35, 4739.43, 39413.07, 37827.19, 9056.71, 49265.35, 35348.52, 10957.42, 30322.44, 22973.19, 47500.12, 48545.9, 946.19, 19273.15, 22761.02, 40868.44, 775.85, 49775.87, 28345, 52012.99, 30051.59, 45289.68, 36900.8, 31460.81, 51110.19, 12397.24, 34578.01, 47720.53, 15712.89, 6756.28, 25762.61, 54078.42, 50010.54, 16609.55, 13129.16, 32402.43, 26270.31, 5681.48, 31295.89, 5824.84, 12550.59, 50847.39, 32752.45, 18003.45, 53884.61, 32547.36, 47514.46, 5961.97, 24958.12, 51028.81, 26937.62, 31497.45, 16485.2, 13467.6, 35387.98, 14217.41, 18260.78, 45292.45, 15902.32, 15645.63, 48198.75, 26858.24, 28383.47, 5838.99, 35127.11, 26490.36, 21835.39, 3394.33, 55933.54, 55142.88, 39673.89, 4071.1, 44240.5, 39103.1, 45423, 15899.4, 372.21, 4810.24, 15848.72, 10573.71 ] }, { "type": "bar", "stack": "总量", "name": "GPU", "barMaxWidth": 40, "label": { "show": false, "position": "inside" }, "large": true, "data": [ 45656.8, 48919.63, 30686.44, 39105.29, 6583.58, 16579.54, 28757.36, 48880.32, 34710.97, 52267.01, 35244.55, 29465.79, 18699.38, 26708.27, 48610.16, 41065.65, 11168.56, 52782.18, 50920.27, 8510.37, 1054.53, 15473.91, 52598.26, 20234.36, 54710.49, 37693.7, 25479.18, 15601.02, 715.52, 47497.22, 30241.03, 15098.62, 36223.61, 39782.68, 31656.7, 52831.1, 4355.73, 13741.26, 2690.1, 8953.15, 40778.42, 40186.61, 29139.39, 34951.79, 29297.1, 13981.23, 50465.4, 52068.09, 24882.09, 11735.73, 24719.71, 26538.39, 54061.69, 17362.82, 27552.54, 39175.74, 35403, 45847.45, 39241.42, 21423.24, 28252.3, 15936.17, 51460.45, 38450.2, 36549.7, 8833.54, 49552.81, 11116.06, 7629.68, 23024.14, 46621.6, 48414.22, 34057.44, 16508.14, 8695.67, 9935.44, 23205.87, 459.17, 6058.89, 11538.15, 1691.72, 5674.09, 43659.65, 42399.6, 23885.54, 16819.74, 18714.28, 51726.61, 12924.85, 9741.92, 47726.78, 52780.24, 19201.93, 17926.32, 42666.5, 8034.12, 1942.08, 47110.64, 46218.2, 44514.71, 31623.2, 42708.9, 26215.12, 28139.43, 32497.47, 45742.64, 38990.36, 4446.69, 44520.51, 20765.67, 18069.18, 6911.16, 5579.52, 29354.69, 17215.26, 26342.73, 11537.4, 16678.75, 37653.87, 3944.92, 15016.5, 31703.34, 46996.72, 4255.04, 36405.66, 15890.87, 8085.44, 8727.37, 20420, 11387.02, 50816.76, 38010.23, 44139.63, 1437.55, 19776.06, 5491.32, 1402.23, 18269.63, 31190.68, 9546.1, 52578.27, 26100.02, 8729.68, 37343.21, 2299.31, 28782.83, 31684.85, 16185.26, 15100.92, 29155.27, 38192.58, 16588.44, 18377.32, 54143.88, 51351.67, 43105.14, 46469.82, 22805.7, 3730.43, 29585.85, 21114.32, 26529.31, 15564.97, 615.24, 32499.96, 43994.11, 36856.41, 19624.48, 7703.24, 17755.76, 47110.48, 54746.48, 23024.8, 3153.74, 39398.46, 31583.36, 9440.84, 10548.38, 8578.17, 17212.49 ] } ] } 那问题肯定基本上就能定位是 v-charts在封装的时候对相关的配置信息做了些变更。于是,使用 v-charts 组件的 afterSetOption 获取最终的配置信息:控制台打印的最终 echarts 配置信息如下:简单清理一些data数据后,使用 Beyond 进行数据对比,还真发现使用 v-charts 转换后的数据存在很大问题:然后进一步跟踪相关源代码,发现在图表组件初始化的时候,不知为何将 xAxis 初始化为2个对象的数组:然后在 optionsHandler 方法中将 extend 中的数据附加到option上就改变了原有数据然后 setExtend 方法会把xAxis对象数据复制到目标数组内的每一个对象值里:解决方案问题分析过程中,认真阅读了下相关的核心源码,发现 v-charts 组件在执行echarts.setOption()前提供了 afterConfig的扩展点,可以基于该扩展点对前期生成的 option 进行修改:为解决该问题,修改后的核心代码如下:修复后的效果总结项目开发过程中出现问题往往不可怕,最重要的是需要静下心来定位问题,当知道问题的产生缘由后,往往解决问题的方法也就水到渠成了。
2023年05月24日
16 阅读
0 评论
0 点赞
2023-05-14
FRP服务端安装并设置开机自启动
背景介绍之前一直有使用FRP做内网穿透,一般主要用于远程桌面和对外提供http服务进行测试,由于域名备案信息被撤销,原有解析过去的二级域名被拦截了,所以考虑将FRP 服务端部署到一台墙外的服务器上去,搜了一圈没找到之前的部署记录,本次也就只好将安装过程记录备忘了。安装时间:2023-05-14 15:51安装下载最新版本的frpfrp 在GitHub的地址是,# 进入用户目录 cd /usr/local # 下载frp 最新版, wget https://github.com/fatedier/frp/releases/download/v0.48.0/frp_0.48.0_linux_amd64.tar.gz # 解压 tar -zxvf frp_0.48.0_linux_amd64.tar.gz配置打开服务端配置文件vi /usr/local/frp/frps.ini 参考frps_full.ini,将服务端配置文件(frps.ini)修改为如下内容[common] bind_addr = 0.0.0.0 bind_port = 7000 token = TOKEN_INFO # udp port to help make udp hole to penetrate nat bind_udp_port = 7001 # if you want to support virtual host, you must set the http port for listening (optional) # Note: http port and https port can be same with bind_port vhost_http_port = 80 vhost_https_port = 443 # console or real logFile path like ./frps.log log_file = ./frps.log # set dashboard_addr and dashboard_port to view dashboard of frps # dashboard_addr's default value is same with bind_addr # dashboard is available only if dashboard_port is set dashboard_addr = 0.0.0.0 dashboard_port = 7500 # dashboard user and passwd for basic auth protect dashboard_user = admin dashboard_pwd = admin # dashboard TLS mode dashboard_tls_mode = false # authentication_method specifies what authentication method to use authenticate frpc with frps. # If "token" is specified - token will be read into login message. # If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token". authentication_method = token # if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file # when subdomain is test, the host used by routing is test.frps.com subdomain_host = lan.dev.trswcm.com自启动添加自启动文件vi /etc/systemd/system/frps.service输入如下内容:[Unit] Description=Frp Server Service After=network.target [Service] Type=simple User=root Restart=on-failure RestartSec=5s ExecStart=/usr/local/frp/frps -c /usr/local/frp/frps.ini [Install] WantedBy=multi-user.target添加可执行权限chmod +x /etc/systemd/system/frps.service注册服务systemctl enable frps.service当出现如下提示信息后表示注册成功 [root@VM-0-9-centos systemd]# systemctl enable frps.service Created symlink from /etc/systemd/system/multi-user.target.wants/frps.service to /etc/systemd/system/frps.service.启动服务systemctl daemon-reload systemctl start frps启动过程有可能会报错,报错的话,请根据错误提示进行相应调整查看服务状态systemctl status frps出现如下截图所示信息则表示已启动成功:推荐另一种简单的docker-compose 的方式:‵‵‵ yamlversion: '3.3'services:frps: restart: always network_mode: host volumes: - './frps.ini:/etc/frp/frps.ini' container_name: frps image: snowdreamtech/frps
2023年05月14日
49 阅读
0 评论
0 点赞
2023-03-20
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
现象使用windows 自带的远程桌面工具 mstsc 远程另一台主机时提示用户把账号信息已锁定:完整错误信息如下:[Window Title] 远程桌面连接 [Content] 为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。请稍候片刻再重试,或与系统管理员或技术支持联系。 [^] 隐藏详细信息(D) [确定] [Expanded Information] 错误代码: 0xd07 扩展错误代码: 0x0 时间戳 (UTC): 03/20/23 06:37:34 AM 按Ctrl+C进行复制。解决一、进入组策略命令行输入 gpedit.msc,在打开的 本地组策略编辑器中打开如下路径:计算机配置 —>Windows设置 —> 安全设置 —> 账户策略 —> 账户锁定策略,如下图所示:修改限制双击「帐户锁定阈值」策略,在打开的对话框中将值修改为 0,如下图所示:修改后,单击「确定」按钮保存即可。
2023年03月20日
1,147 阅读
0 评论
0 点赞
2023-03-15
峰终定律
概述峰终定律是指人们对一件事的印象,往往只能记住两个部分。一个是过程中的最强体验,峰;一个是最后的体验,终。过程中好与不好的其他体验对记忆差不多没有影响。一、峰终定律的概念诺贝尔奖得主,心理学家DanielKahneman,经过深入研究,发现对体验的记忆由两个因素决定:高峰(无论是正向的还是负向的)时与结束时的感觉,这就是峰终定律(Peak-EndRule)。这条定律基于潜意识总结体验的特点:对一项事物的体验之后,所能记住的就只是在峰与终时的体验,而在过程中好与不好体验的比重、好与不好体验的时间长短,对记忆差不多没有影响。而这里的“峰”与“终”其实这就是所谓的“关键时刻MOT”,MOT(MomentofTruth)是服务界最具震撼力与影响力的管理概念与行为模式。人们主要根据他们在高峰时期(即最紧张的时刻)感受到的体验来判断体验,而不是基于每个时刻的总和或平均体验。二、峰终定律的体验心理学家卡纳曼付钱让受试者亲自体验A和B,然后问他们愿意再接受哪一种体验:A 把手放在冰冷刺骨的水里60秒。B 把手放在冰冷刺骨的水里90秒,前60秒水温不变,后30秒温度慢慢上升,虽然水依然很冰,但至少比刚才好一点。所有人都会觉得选项A比较好,因为不愉快的体验少了30秒,没有人会喜欢在不愉快的体验之后再追加其他不愉快的体验。不过实际情况并非如此。出乎意料的是,80%以上的人选择了时间比较长的B,为什么会出现这种结果呢?只有亲身体验过喜悦和痛苦之后,人们才能对其作出适合自己感受的衡量。在事前预测阶段,人们的心理认知未必符合实际感受情况。从客观角度来看,选项B应该比较痛苦,所以大家倾向于选择痛苦看起来比较少的选项A。可是在实际体验过A和B以后,人们反而会选择感受和记忆相对比较美好的B。卡纳曼经过深入研究,发现人的记忆不会以同等标准衡量所有事物,而会牵涉到与这些事物有关的复杂情绪。衡量某种体验时,人们容易忽略该体验持续的整体印象,而经常根据高峰(无论是正向的还是负向的)时和结束时的感觉作出判断。简言之,我们在实际体验之前,会选择痛苦比较少的选项,而亲身体验之后,即使痛苦时间比较长,也会选择记忆印象比较好的选项。三、峰终定律的应用任何消费者互动中的负面事件都可以通过建立牢固的积极高峰和结束点来抵消。 这可以通过演奏顾客喜欢的音乐,赠送免费样品,或者让店员在顾客离开时为顾客保持门而实现。 正如Scott Stratten所建议的那样,“一位真正优秀的销售人员可以帮助交流,消除一路上的负面经历。”排长队排队,更衣室里的糟糕音乐被遗忘了。宜家的购物路线是按照“峰终定律”设计。虽然在宜家购物有一些不好的体验,比如“地形”复杂,哪怕只买一件家具也需要走完整个商场,比如店员很少,找不到帮助,比如要自己从货架上搬货物,要排长队结账等等等等。但是它的峰终体验是好的,它的“峰”就是过程中的小惊喜,比如便宜又好用的挂钟,好看的羊毛毯以及著名的瑞典肉丸;它的“终”是出口处 1 元钱的冰淇淋!如果没有出口处 1 元钱的冰淇淋,宜家的“终”体验可能会很差。所以,1 元钱的甜筒看似赔本,却为宜家带来了极佳的“终”体验,成为人们记住宜家的一个标记。当人们再回忆起宜家的购物之旅时,会觉得整体行程都非常棒。客户/合作伙伴来拜访,不管过程如何,在客户/合作伙伴离开的时候,一定送客户/合作伙伴到门口或者电梯间,并在电梯门关上前的瞬间,给客户/合作伙伴一个灿烂的笑容。
2023年03月15日
13 阅读
0 评论
0 点赞
2023-03-14
用*输出一个等腰三角形
缘起工作间隙,得到一个刚学编程时的题目,题目如下:用输出一个等腰三角形。提示用户输入一个整数 ,代表输出的等边三角形由n行组成。例:输入n=5。输出: * *** ***** ******* *********朋友提供的代码如下:public static void main(String[] args) { System.out.println("请输入需要的行数(不可输入0):"); Scanner scanner = new Scanner(System.in); int rows = scanner.nextInt(); int rowIndex = rows - 1; //最后一行总个数 int total = 1 + (rows - 1) * 2; // 每行*个数 int num = 1; // 打印标记 boolean flag = false; if (rows != 0) { for (int i = 0; i < rows; i++) { for (int j = 0, k = 0; j < total; j++) { // 倒数第几行就从第几个下标开始 if (j == rowIndex - i) { flag = true; } if (flag && k < num) { System.out.print("*"); // 开始打印计数 k++; } else { System.out.print(" "); // 结束打印 flag = false; } } num = num + 2; System.out.println(); } } }整段代码看下来实现方式有些复杂,感觉有些陷在循环里了,脑袋里不跟着一行一行的跑代码的话,不怎么能想出来结果输出是怎样的。于是,我从头开始梳理了下需要输出的东西,主要是要从中找寻规律:第一步:手工输出前几个结果,从结果中找规律2: * *** 3: * *** ***** 4: * *** ***** ******* 5: * *** ***** ******* ********* 6: * *** ***** ******* ********* *********** 7: * *** ***** ******* ********* *********** *************第二步:分析得出如下规律:令 行数为1开始,当前行的输出情况如下:星号数量 = 当前行数 * 2 - 1左侧空格数量 = 总行数 - 当前行数得出规律后,就能够很好的着手后面的代码工作了,以下是我提供的代码:JS版:/** * 输出三角形 * @param rows 三角形行数 */ let showTriangle = (rows) => { console.log(`输出${rows}等腰三角形:`) // 如果小于2的话不能构成等腰三角形 if (rows < 2) { console.error('必须大于1') return; } for (let row = 1; row <= rows; row++) { // 输出空格 let rowStr = '' for (let blankIndex = 0; blankIndex < rows - row; blankIndex++) { rowStr += ' ' } // 输出星号 for (let starIndex = 0; starIndex < row * 2 - 1; starIndex++) { rowStr += '*' } console.log(rowStr) } } // 测试输出: for (let i = 0; i < 15; i++) { showTriangle(i) }输出结果:Java 版由于使用了 String.repeat(), JDK 版本应 >= 11,若 JDK 低于 11 也可以用循环实现。import java.util.InputMismatchException; import java.util.Scanner; /** * @author zhuzl */ public class ShowTriangle { public static void main(String[] args) { System.out.println("请输入三角形的行数(需大于1):"); Scanner scanner = new Scanner(System.in); int rows = scanner.nextInt(); printTriangle(rows); } /** * 打印等腰三角形 * * @param rows 三角形行数 */ private static void printTriangle(int rows) { if (rows < 2) { throw new InputMismatchException("必须大于1"); } for (int row = 1; row <= rows; row++) { System.out.println(" ".repeat(rows - row) + "*".repeat(row * 2 - 1)); } } }输出结果如下:
2023年03月14日
16 阅读
0 评论
0 点赞
2022-11-04
CentOS安装LibreOffice记录
LibreOffice安装下载地址:https://zh-cn.libreoffice.org/download/libreoffice/下载最新的Windows版本,目前最新文档版本是7.3.7。下载后正常安装即可。下载LibreOffice:# 下载主安装程序 wget https://mirrors.ustc.edu.cn/tdf/libreoffice/stable/7.3.7/rpm/x86_64/LibreOffice_7.3.7_Linux_x86-64_rpm.tar.gz # 下载中文字体支持包 wget https://mirrors.ustc.edu.cn/tdf/libreoffice/stable/7.3.7/rpm/x86_64/LibreOffice_7.3.7_Linux_x86-64_rpm_langpack_zh-CN.tar.gz解压tar -zxvf LibreOffice_7.3.7_Linux_x86-64_rpm.tar.gz安装# 进入安装目录 cd LibreOffice_7.3.7_Linux_x86-64_rpm/RPMS/ # 安装 yum localinstall -y *.rpm程序路径:/opt/libreoffice7.3/program/soffice启动nohup /opt/libreoffice7.3/program/soffice --headless --accept="socket,host=127.0.0.1,port=8100;urp;" --nofirststartwizard &
2022年11月04日
71 阅读
0 评论
0 点赞
2022-11-04
CentOS 安装 OpenOffice记录
项目有需要用到OpenOffice进行word文档解析,需在服务器部署OpenOffice,以下为安装记录。下载地址:https://www.openoffice.org/download/index.html当前最新版本是4.1.13。下载后正常安装即可。下载wget http://jaist.dl.sourceforge.net/project/openofficeorg.mirror/4.1.13/binaries/zh-CN/Apache_OpenOffice_4.1.13_Linux_x86-64_install-rpm_zh-CN.tar.gz解压tar -zxvf Apache_OpenOffice_4.1.13_Linux_x86-64_install-rpm_zh-CN.tar.gz安装cd zh-CN/RPMS/ rpm -ivh *.rpm 启动服务nohup /opt/openoffice4/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard >/dev/null 2>&1 & 若启动过程中提示如下信息:no suitable windowing system found, exiting.。可执行如下代码安装缺失的依赖。yum groupinstall "X Window System"自启动将以上启动服务的代码添加到 /etc/rc.d/rc.local最底部,或自行参考Linux添加服务的方式实现自启动。
2022年11月04日
29 阅读
0 评论
0 点赞
1
2
3
4
...
8