Windows安装nacos

下载安装包 https://github.com/alibaba/nacos/releases 访问GitHub下载当前最新的安装包,解压到安装目录 单机模式支持MySQL 1.安装数据库,版本要求:5.6.5+ 2.初始化mysql数据库,数据库初始化文件:mysql-schema.sql 3.修改nacos/conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。 启动 Nacos启动需要准备64 bit JDK 1.8+环境。 启动命令(standalone代表着单机模式运行,非集群模式): startup.cmd -m standalone Nacos默认是集群模式,也可以修改配置改为单机模式启动。 路径: nacos/bin/startup.cmd

2022/11/14
articleCard.readMore

Edge用户登录一直转圈

最近在Edge浏览器右上角登录微软账号的时候,提示0x80190001无法登录。 通过百度查到如下解决办法: 1.在控制面板或者IE浏览器中打开Internet选项,删除Cookie信息。 2.在Internet选项-“高级”菜单中,点击重置,重置网络设置。 操作以后不提示0x80190001错误信息了,但是一直在转圈,还是没办法登录。 最后通过设置成微软的DNS解决了

2022/11/14
articleCard.readMore

SpringBoot整合Socket

前言 前段时间公司一个物联网项目需要通过TCP连接设备收发消息,现在我把代码整理出来,分享一下。 源代码已发布在GitHub 创建Socket 使用ServerSocket绑定IP和端口, TcpSocket实现Java的Runnable的类,在run方法中使用Accept监听端口是否有客户端发送连接请求,如果有连接来了就创建SocketReceive对象然后将他扔给线程池执行。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.example.socket_demo.socket; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author Administrator */ @Component @Slf4j public class TcpSocket implements Runnable { public Integer port; private ServerSocket server; private ExecutorService threadPool; public TcpSocket() { try { port = 8081; threadPool = Executors.newCachedThreadPool(); server = new ServerSocket(port); } catch (Exception e) { log.error(e.getMessage()); } } @Override public void run() { while (true) { try { Socket socket = server.accept(); if (socket != null) { SocketReceive socketReceive = new SocketReceive(socket); threadPool.submit(socketReceive); } } catch (IOException e) { e.printStackTrace(); } } } } 在线连接 AllClientsMap类存放了所有的在线连接,通过hostAddress为key,Socket为Value。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 package com.example.socket_demo.socket; import lombok.extern.slf4j.Slf4j; import java.net.Socket; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Slf4j public class AllClientsMap { /** * 所有已连接设备 */ private static final ConcurrentMap<String, Socket> ALLCLIENTS = new ConcurrentHashMap<>(); /** * 返回设备列表 * * @return */ public static ConcurrentMap<String, Socket> getAllClients() { return ALLCLIENTS; } /** * 通过key获取客户端 * * @return */ public static Socket getSocketByKey(String key) { return ALLCLIENTS.get(key); } /** * 添加设备到列表 * * @param key * @param socket */ public static void put(String key, Socket socket) { ALLCLIENTS.put(key, socket); log.info("设备Key:{}========ip:{}已加入列表", key, socket.getInetAddress().getHostAddress()); } /** * 移除设备 * * @param key */ public static void remove(String key) { ALLCLIENTS.remove(key); log.info("已移除设备Key:{}", key); } /** * 返回已连接设备数量 * * @return */ public static int size() { log.info("当前设备数:{}", ALLCLIENTS.size()); return ALLCLIENTS.size(); } /** * 打印信息 * * @return */ public static void print() { log.info("当前设备列表信息:长度:{}", ALLCLIENTS.size()); ALLCLIENTS.forEach((key, socket) -> { log.info("设备Key:{}========ip:{}", key, socket.getInetAddress().getHostAddress()); }); } /** * 是否包含 * * @param key * @return */ public static boolean contains(String key) { return ALLCLIENTS.containsKey(key); } } 创建SocketReceive 在SocketReceive类中,我们可以执行相关的接收消息,以及业务操作;在第64行代码的位置,可以通过ApplicationContext获取Spring Bean执行业务代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 package com.example.socket_demo.socket; import lombok.extern.slf4j.Slf4j; import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; @Slf4j public class SocketReceive implements Runnable { private Socket socket; public SocketReceive() { } public SocketReceive(Socket socket) { this.socket = socket; } @Override public void run() { while (true) { if (null == socket) { log.info("socket为空"); return; } boolean isClosed = socket.isClosed(); String hostAddress = socket.getInetAddress().getHostAddress(); if (isClosed) { log.info("socket检测到关闭了"); if (AllClientsMap.contains(hostAddress)) { AllClientsMap.remove(hostAddress); AllClientsMap.print(); } return; } String hostAddress = socket.getInetAddress().getHostAddress(); try { //建立客户端信息输入流 DataInputStream in = new DataInputStream(socket.getInputStream()); //定义字节数组读取数据 byte[] bytes = new byte[1024]; int len = in.read(bytes); if (len == -1) { return; } //定义一个新数组copy,解决读取出来的数据字节不够全是0的问题 byte[] bytes1 = new byte[len]; System.arraycopy(bytes, 0, bytes1, 0, len); log.info("客户端传的byte字节数组:" + printBytesByStringBuilder(bytes1)); String s = new String(bytes1); log.info("客户端传的byte字节数组转换成字符串打印:" + s); //转换hex数据 String data = byteArrayToHex(bytes1); log.info("接收的16进制数据:" + data); //如果服务端没有保存该socket if(!AllClientsMap.contains(hostAddress)){ AllClientsMap.put(hostAddress, socket); AllClientsMap.print(); } log.debug("客户端" + hostAddress + "发送数据:{}", data); //执行业务 System.out.println("执行业务"); //从map中获取客户端发送消息 response(AllClientsMap.getSocketByKey(hostAddress), data); } catch (IOException e) { if (AllClientsMap.contains(hostAddress)) { AllClientsMap.remove(hostAddress); AllClientsMap.print(); } try { socket.close(); log.error("{}断开连接", hostAddress); return; } catch (IOException ioException) { log.error(ioException.getMessage()); } } } } /** * 根据字节数组,输出对应的格式化字符串 * * @param bytes 字节数组 * @return 字节数组字符串 */ public static String printBytesByStringBuilder(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(); for (byte aByte : bytes) { stringBuilder.append(byte2String(aByte)); } return stringBuilder.toString(); } public static String byte2String(byte b) { return String.format("%02x ", b); } /** * 向socket发送消息 * * @param socket 对应socket * @param msg 消息 */ public static void response(Socket socket, String msg) { log.debug("向设备IP:{}发送消息:{}", socket.getInetAddress().getHostAddress(), msg); OutputStream outputStream = null; try { outputStream = socket.getOutputStream(); outputStream.write(hexStringToByteArray(msg)); } catch (IOException e) { try { socket.close(); } catch (IOException ioException) { log.error(ioException.getMessage()); } log.error(e.getMessage()); } } /** * 字节数组转字符串 * * @param bytes * @return */ public static String byteArrayToHex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (int index = 0, len = bytes.length; index <= len - 1; index += 1) { int char1 = ((bytes[index] >> 4) & 0xF); char chara1 = Character.forDigit(char1, 16); int char2 = ((bytes[index]) & 0xF); char chara2 = Character.forDigit(char2, 16); result.append(chara1); result.append(chara2); } return result.toString(); } /** * 16进制表示的字符串转换为字节数组 * * @param hexString 16进制表示的字符串 * @return byte[] 字节数组 */ public static byte[] hexStringToByteArray(String hexString) { hexString = hexString.replaceAll(" ", ""); int len = hexString.length(); byte[] bytes = new byte[len / 2]; for (int i = 0; i < len; i += 2) { // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节 bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character .digit(hexString.charAt(i + 1), 16)); } return bytes; } } 启动 通过继承Spring的InitializingBean类,重写afterPropertiesSet方法,这个方法将在所有的属性被初始化后调用。 然后会创建一个线程执行ServerSocket的监听,初始化我们的TcpSocket对象,一旦Server接收到了连接请求后,会创建一个SocketReceive对象将其扔给线程池执行,在线程池中的SocketReceive对象可以通过ApplicationContext获取Spring Bean执行业务代码。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.socket_demo.socket; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class SpringFinishedListener implements InitializingBean { @Autowired private TcpSocket tcpsocket; @Override public void afterPropertiesSet() { Thread serverThread = new Thread(tcpsocket); serverThread.start(); } } 测试工具 这里推荐一个测试工具,还挺好用的。下载连接

2022/10/27
articleCard.readMore

使用coding持续集成SpringBoot项目

前言 公司项目使用Coding进行管理,每次打包部署都需要经历以下流程 代码提交 项目打包 登录服务器 上传应用到服务器 执行部署脚本 发现Bug ->修改Bug -> 重复第一道流程 了解过Jenkins等持续集成工具后,查看到Coding也有相关服务,并且Coding还提供一台云主机来进行构建操作。 如果你也长期经历以上流程,强烈建议你了解一下持续集成,因为手动部署实在是麻烦又耗时,使用持续集成后省下的时间又可以多写两行Bug了🤪 需求 我们希望每次写完代码,提交到主分支以后,项目能够自动编译打包,上传至服务器,并自动重启应用 前期工作 凭证管理 官方文档,凭证管理 https://coding.net/help/docs/project-settings/credential.html 生成Rsa私钥 登录服务器,生成Rsa私钥 1 ssh-keygen -t rsa 生成好的文件在/root/.ssh路径下 将id_rsa.pub添加到authorized_keys文件中,并重启sshd服务 1 systemctl restart sshd 添加至Coding凭证管理 1.将id_rsa文件中的内容添加至Coding 2.在录入凭据页面,输入相关信息 选择「SSH 私钥」凭据类型 输入凭据名称,必填项,长度不超过 255 个字符 输入 SSH 私钥,必填项 输入私钥口令,非必填 输入描述,非必填 3.勾选需要授权的持续集成功能。只有进行凭据授权后,在使用 CODING 持续集成功能模块创建构建计划时才有权限使用该凭据。 4.点击「创建」即可完成创建。录入成功的凭据会显示在凭据管理页面。 白名单释放 因为我们使用的是阿里云的服务器,所以需要释放Coding的IP,如果你使用的不是阿里云可忽略 Coding文档:https://coding.net/help/docs/ci/faq/job-fail.html#aliyun 执行 SSH 命令访问阿里云主机时提示 Connection reset 错误。 此问题是阿里云侧白名单未放行 CODING IP 所致。前往阿里云「安全管控平台」→「安全管控」→「新增访问白名单」,将构建机的 IP 加入至白名单中可以防止其在访问云主机时被拦截。 CODING 构建机所使用的出口 IP 如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 中国上海节点 111.231.92.100 81.68.101.44 # 中国香港节点 124.156.164.25 119.28.15.65 # 美国硅谷节点 170.106.136.17 170.106.83.77 构建计划 创建构建计划 首先进入Coding的项目中,找到持续集成-构建计划,新建构建计划 点击自定义构建流程 Jenkinsfile 创建自定义流程以后我们开始编写Jenkinsfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 pipeline { agent any stages { stage('检出') { steps { checkout([ $class: 'GitSCM', branches: [[name: GIT_BUILD_REF]], userRemoteConfigs: [[ url: GIT_REPO_URL, credentialsId: CREDENTIALS_ID ]]]) } } stage('编译') { steps { echo '构建中...' sh 'mvn clean package' echo '构建完成' echo '当前所在位置:' sh '''pwd ls''' sh '''ls ${project_target_path} ''' sh '''tar -zcf /root/workspace/tmp.tar.gz ${project_target_path}/*.jar ls /root/workspace/''' } } stage('重启应用') { steps { echo '开始发送文件到远端服务器...' script { def remote = [:] remote.name = 'web-server' remote.allowAnyHosts = true remote.host = host remote.port = port as Integer remote.user = user // 把「CODING 凭据管理」中的「凭据 ID」填入 credentialsId,而 id_rsa 无需修改 withCredentials([sshUserPrivateKey(credentialsId: credentialsId, keyFileVariable: 'id_rsa')]) { remote.identityFile = id_rsa // SSH 上传文件到远端服务器 sshPut remote: remote, from: "/root/workspace/tmp.tar.gz", into: '/tmp/' // 解压缩 sshCommand remote: remote, command: "tar -zxf /tmp/tmp.tar.gz -C /tmp/" //复制文件到运行目录 sshCommand remote: remote, sudo: true, command: "cp -R /tmp/${project_target_path}/* ${project_path}" //执行脚本重启应用 sshCommand remote: remote, sudo: true, command: sh_path_command } } echo '部署成功...' } } } } Jenkinsfile文件中有七个环境变量字段,如下 字段值字段名称字段注释示例 project_path项目存放的目录地址jar在服务器上实际存放的地址/usr/local/java/demo sh_path_command需要执行的sh命令一般是 sh 脚本所在位置加执行的操作sh /usr/local/java/demo/start.sh restart 此命令是执行/usr/local/java/demo目录下start.sh脚本的restart操作 host:目标服务器地址服务器IP地址111.111.111.111 portSSH端口号端口号22 userSSH用户名登录用户名root credentialsIdSSH登录凭据ssh登录凭证选择项,需要配置私钥并添加至coding凭证管理中 project_target_pathjar包所在目录执行打包命令后jar所在的目录demo/target 以上环境变量需要添加至该构建计划的【变量与缓存】中,如图: 我们的项目采用Maven构建,我勾选上了缓存Maven目录,这样每次构建就不需要重复下载依赖了,速度更快 启动脚本 此脚本来自ruoyi-vue项目,脚本内容需要调整$AppName和$APP_HOME参数,$JVM_OPTS参数可酌情调整 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #!/bin/sh # ./start.sh start 启动 # ./start.sh stop 停止 # ./start.sh restart 重启 # ./start.sh status 状态 AppName=jenkins_demo-0.0.1-SNAPSHOT.jar # JVM参数 JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512M -Xmx512M -XX:PermSize=256M -XX:MaxPermSize=512M -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" #App所在目录 APP_HOME="/usr/local/java/demo" LOG_PATH=$APP_HOME/logs/$AppName.log if [ "$1" = "" ]; then echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" exit 1 fi if [ "$AppName" = "" ]; then echo -e "\033[0;31m 未输入应用名 \033[0m" exit 1 fi function start() { echo "开始执行start" cd $APP_HOME nohup java -jar $APP_HOME/$AppName > nohup.out & 2>&1 & echo "结束执行start" } function stop() { echo "Stop $AppName" PID="" query(){ PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` } query if [ x"$PID" != x"" ]; then kill -TERM $PID echo "$AppName (pid:$PID) exiting..." while [ x"$PID" != x"" ] do sleep 1 query done echo "$AppName exited." else echo "$AppName already stopped." fi } function restart() { stop sleep 2 start } function status() { PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` if [ $PID != 0 ];then echo "$AppName is running..." else echo "$AppName is not running..." fi } case $1 in start) start;; stop) stop;; restart) restart;; status) status;; *) esac 疑问/讨论 如果使用Nginx配置了负载均衡,部署了多个Jar,该持续集成应该怎么实现呢?评论区有答案吗?

2022/10/17
articleCard.readMore

使用acme.sh来申请SSL证书

简介 acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书. 官方文档:https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E 本篇文章以Nginx为例子,来实现获取SSL证书,以及配置Nginx 安装 安装很简单, 一个命令: 1 curl https://get.acme.sh | sh -s email=my@example.com my@example.com是你的邮箱地址 创建 一个 shell 的 alias, 例如 .bashrc,方便你的使用: 1 alias acme.sh=~/.acme.sh/acme.sh 普通用户和 root 用户都可以安装使用. 安装过程进行了以下几步: 把 acme.sh 安装到你的 home 目录下: 1 ~/.acme.sh/ 自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, 如果快过期了, 需要更新, 则会自动更新证书. 更高级的安装选项请参考: https://github.com/Neilpang/acme.sh/wiki/How-to-install 配置Nginx 先把Nginx配置文件写好,让这个域名可以通过http访问 1 2 3 4 5 6 7 8 9 10 11 12 server { listen 80; server_name 你的域名; location / { #转发的ip和端口 proxy_pass http://127.0.0.1:8080; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } 生成证书 关于生成证书,官方的例子有apache、nginx、手动 dns 的方式,我这里只演示Nginx的方式 官方文档上这样写的 https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E#1-http-%E6%96%B9%E5%BC%8F%E9%9C%80%E8%A6%81%E5%9C%A8%E4%BD%A0%E7%9A%84%E7%BD%91%E7%AB%99%E6%A0%B9%E7%9B%AE%E5%BD%95%E4%B8%8B%E6%94%BE%E7%BD%AE%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6-%E6%9D%A5%E9%AA%8C%E8%AF%81%E4%BD%A0%E7%9A%84%E5%9F%9F%E5%90%8D%E6%89%80%E6%9C%89%E6%9D%83%E5%AE%8C%E6%88%90%E9%AA%8C%E8%AF%81-%E7%84%B6%E5%90%8E%E5%B0%B1%E5%8F%AF%E4%BB%A5%E7%94%9F%E6%88%90%E8%AF%81%E4%B9%A6%E4%BA%86 如果你用的 nginx服务器, 或者反代, acme.sh 还可以智能的从 nginx的配置中自动完成验证, 你不需要指定网站根目录: 执行以下命令: 1 acme.sh --issue -d 你的域名.com --nginx 生成好的证书会存放在~/.acme.sh/example.com下 copy/安装证书 前面证书生成以后, 接下来需要把证书 copy 到真正需要用它的地方. 注意, 默认生成的证书都放在安装目录下: ~/.acme.sh/, 请不要直接使用此目录下的文件, 例如: 不要直接让 nginx/apache 的配置文件使用这下面的文件. 这里面的文件都是内部使用, 而且目录结构可能会变化. 正确的使用方法是使用 –install-cert 命令,并指定目标位置, 然后证书文件会被copy到相应的位置, 例如: 1 2 3 4 5 acme.sh --force --install-cert -d example.com --fullchain-file /etc/nginx/ssl/example.com.crt --key-file /etc/nginx/ssl/example.com.key --reloadcmd "service nginx force-reload" > /dev/null –install-cert: 这个参数代表cpoy -d: 你的域名 –key-file: copy你的私钥文件,需要和nginx指定的一致 –fullchain-file: copy你的证书链文件(此文件包含自己和中间ca机构的证书),需要和nginx指定的一致 –reloadcmd: 指定重新加载证书的命令 (一个小提醒, 这里用的是 service nginx force-reload, 不是 service nginx reload, 据测试, reload 并不会重新加载证书, 所以用的 force-reload) Nginx 的配置 ssl_certificate 使用 /etc/nginx/ssl/fullchain.cer ,而非 /etc/nginx/ssl/.cer ,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。 –install-cert命令可以携带很多参数, 来指定目标文件. 并且可以指定 reloadcmd, 当证书更新以后, reloadcmd会被自动调用,让服务器生效. 详细参数请参考: https://github.com/Neilpang/acme.sh#3-install-the-issued-cert-to-apachenginx-etc 值得注意的是, 这里指定的所有参数都会被自动记录下来, 并在将来证书自动更新以后, 被再次自动调用. 修改Nginx配置文件 执行上面的命令以后,会发现在/etc/nginx/ssl目录下会多出两个文件,我们需要把这两个文件在Nginx配置文件中配置好 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 server { listen 443 ssl; server_name example.com; ssl on; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } Nginx配置好后重启Nginx,访问你的域名。 更新证书 目前证书在 60 天以后会自动更新, 你无需任何操作. 今后有可能会缩短这个时间, 不过都是自动的, 你不用关心. 请确保 cronjob 正确安装, 看起来是类似这样的: 1 2 3 crontab -l 56 * * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null 其他常用命令 acme.sh --info -d example.com :查看已安装证书信息 acme.sh --list: 列出所有证书 acme.sh --renew -d soulchild.site -d *.soulchild.site --force: 手动强制更新证书 acme.sh --renew-all: 手动更新所有证书 acme.sh --revoke: 撤销证书 acme.sh --remove -d soulchild.site: 删除证书 acme.sh --cron: 通过cronjob更新所有证书。 acme.sh --upgrade: 升级acme.sh acme.sh --uninstall: 卸载acme.sh 更新acme.sh acme.sh --upgrade:升级到最新版 acme.sh --upgrade --auto-upgrade:开启自动更新 acme.sh --upgrade --auto-upgrade 0:关闭自动更新 参考 https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E https://github.com/oneinstack/oneinstack/blob/master/vhost.sh#L265

2022/9/23
articleCard.readMore

CentOS安装Nginx

简介 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的。 其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好。 安装 添加Nginx到YUM源 1 rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 安装Nginx 1 yum -y install nginx 启动Nginx 刚安装的Nginx不会自动启动 1 systemctl start nginx.service 查看Nginx状态 1 2 systemctl enable nginx.service 设置开机自启 1 systemctl status nginx.service 停止Nginx 1 systemctl stop nginx.service 重启Nginx 1 systemctl restart nginx.service 重新读取nginx配置 这个最常用, 不用停止nginx服务就能使修改的配置生效 1 systemctl reload nginx.service 网站文件存放默认目录 1 /usr/share/nginx/html Nginx配置文件路径 1 /etc/nginx/nginx.conf

2022/9/23
articleCard.readMore

使用Windows内置虚拟机Hyper-v安装CentOS

下载Centos http://mirrors.aliyun.com/centos/7/isos/x86_64/ 开启Hyper-V 在Windows中搜索启用或关闭Windows功能并打开 勾选Hyper-V 在应用程序中找到hyper-v管理器 虚拟交换机 网上其他文章在新建虚拟机之前会先创建虚拟交换机,但是我操作的时候,创建完虚拟交换机以后,本机会出现无网络的情况 所以我就不创建虚拟交换机了 新建虚拟机 直接点击下一步 设置虚拟机名称以及存放位置 选择第一代 分配内存大小,按你的需求进行分配 网络连接如果是新建了虚拟交换机就选择新建的,我这里采用默认的 创建虚拟硬盘,大小按你需求设置 选择你下载的CentOS 点击完成 启动系统 连接虚拟机 直接回车 选择中文简体 点击开始安装 设置root用户密码 等待安装完成 重启以后,输入账号密码 测试网络 其他问题 Hyper-V:无法打开虚拟机XXX,因为虚拟机监控程序未运行 https://zhuanlan.zhihu.com/p/20408480 https://www.jianshu.com/p/624093c28051 参考 https://blog.csdn.net/qq_36077437/article/details/123980445

2022/9/8
articleCard.readMore

ruoyi-vue项目集成flyway实现自动创建表

添加flyway依赖 在根目录下pom.xml添加flyway依赖RuoYi-Vue\pom.xml 1 2 3 4 5 <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>7.15.0</version> </dependency> 在ruoyi-framework\pom.xml下引入 1 2 3 4 <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> yaml配置 在RuoYi-Vue\ruoyi-admin\src\main\resources\application.yml中添加以下配置 1 2 3 4 5 6 7 8 # 配置flyway数据版本管理 flyway: enabled: true baseline-on-migrate: true clean-on-validation-error: false sql-migration-prefix: V sql-migration-suffixes: .sql locations: classpath:db/migration flyway.baseline-description对执行迁移时基准版本的描述. flyway.baseline-on-migrate当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false. flyway.baseline-version开始执行基准迁移时对现有的schema的版本打标签,默认值为1. flyway.check-location检查迁移脚本的位置是否存在,默认false. flyway.clean-on-validation-error当发现校验错误时是否自动调用clean,默认false. flyway.enabled是否开启flywary,默认true. flyway.encoding设置迁移时的编码,默认UTF-8. flyway.ignore-failed-future-migration当读取元数据表时是否忽略错误的迁移,默认false. flyway.init-sqls当初始化好连接时要执行的SQL. flyway.locations迁移脚本的位置,默认db/migration. flyway.out-of-order是否允许无序的迁移,默认false. flyway.password目标数据库的密码. flyway.placeholder-prefix设置每个placeholder的前缀,默认${. flyway.placeholder-replacementplaceholders是否要被替换,默认true. flyway.placeholder-suffix设置每个placeholder的后缀,默认}. flyway.placeholders.[placeholder name]设置placeholder的value flyway.schemas设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema. flyway.sql-migration-prefix迁移文件的前缀,默认为V. flyway.sql-migration-separator迁移脚本的文件名分隔符,默认__ flyway.sql-migration-suffix迁移脚本的后缀,默认为.sql flyway.tableflyway使用的元数据表名,默认为schema_version flyway.target迁移时使用的目标版本,默认为latest version flyway.url迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源 flyway.user迁移数据库的用户名 flyway.validate-on-migrate迁移时是否校验,默认为true. 创建目录 在RuoYi-Vue\ruoyi-system\src\main\resources目录下创建db/migration目录,把若依若依提供的表结构sql文件复制到该文件夹,并且重命名。 文件命名规则: 仅需要被执行一次的SQL命名以大写的”V”开头,V+版本号(版本号的数字间以”.“或”_“分隔开)+双下划线(用来分隔版本号和描述)+文件描述+后缀名。例如:V20201100__create_user.sql、V2.1.5__create_user_ddl.sql、V4.1_2__add_user_dml.sql。 可重复运行的SQL,则以大写的“R”开头,后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。(不推荐使用)比如:R__truncate_user_dml.sql。 若依项目代码修改 全局搜索一下@PostConstruct,将代码中@PostConstruct注释掉,总共有三个文件需要修改SysJobServiceImpl,SysDictTypeServiceImpl,SysConfigServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) */ //@PostConstruct public void init() throws SchedulerException, TaskException { scheduler.clear(); List<SysJob> jobList = jobMapper.selectJobAll(); for (SysJob job : jobList) { ScheduleUtils.createScheduleJob(scheduler, job); } } 1 2 3 4 5 6 7 8 /** * 项目启动时,初始化字典到缓存 */ //@PostConstruct public void init() { loadingDictCache(); } 1 2 3 4 5 6 7 8 /** * 项目启动时,初始化参数到缓存 */ //@PostConstruct public void init() { loadingConfigCache(); } 注释掉以后,我们把启动时初始化的操作移动到RuoYiApplication.java中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.ruoyi; import com.ruoyi.quartz.service.impl.SysJobServiceImpl; import com.ruoyi.system.service.impl.SysConfigServiceImpl; import com.ruoyi.system.service.impl.SysDictTypeServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; /** * 启动程序 * * @author ruoyi */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class RuoYiApplication implements CommandLineRunner { @Autowired private SysConfigServiceImpl configService; @Autowired private SysJobServiceImpl jobService; @Autowired private SysDictTypeServiceImpl dictTypeService; public static void main(String[] args) { // System.setProperty("spring.devtools.restart.enabled", "false"); SpringApplication.run(RuoYiApplication.class, args); System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" + " .-------. ____ __ \n" + " | _ _ \\ \\ \\ / / \n" + " | ( ' ) | \\ _. / ' \n" + " |(_ o _) / _( )_ .' \n" + " | (_,_).' __ ___(_ o _)' \n" + " | |\\ \\ | || |(_,_)' \n" + " | | \\ `' /| `-' / \n" + " | | \\ / \\ / \n" + " ''-' `'-' `-..-' "); } @Override public void run(String... args) throws Exception { configService.init(); jobService.init(); dictTypeService.init(); } } 启动项目 代码编写好以后,我们在数据库中创建一个ry-vue的数据库,在RuoYi-Vue\ruoyi-admin\src\main\resources\application.yml下修改数据库连接和数据库密码等信息。 启动成功以后,我们会看到ry-vue的数据库中表结构已经创建完成。 参考 https://gitee.com/kidKing/ruoyi-docker-flyway

2022/9/8
articleCard.readMore

Docker常用命令记录

本篇文章以安装MySQL数据库为例子,记录Docker常用命令 安装docker 1 yum install docker -y docker容器互联 新建网络 1 docker network create my-net 列出所有网络 1 docker network ls 将容器连接到网络 1 docker network connect my-net mysql 断开容器的网络 1 docker network disconnect my-net mysql 删除一个或多个网络 1 docker network rm my-net docker客户端 容器使用 搜索镜像 这里我们搜索mysql镜像 1 docker search mysql 获取MySQL镜像 下载需要的版本docker pull mysql:tag tag代表版本号,没有代表是lastest的 1 docker pull docker.io/mysql:5.7.26 列出本地镜像 1 docker images [OPTIONS] [REPOSITORY[:TAG]] OPTIONS说明 -a :列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层); –digests :显示镜像的摘要信息; -f :显示满足条件的镜像; –format :指定返回值的模板文件; –no-trunc :显示完整的镜像信息; -q :只显示镜像ID。 创建 MySQL 数据目录 1 mkdir -p /opt/mysql 启动MySQL实例 1 docker run --name mysql -p 3306:3306 --privileged=true -v /opt/mysql/log:/var/log/mysql -v /opt/mysql/conf:/etc/mysql -v /opt/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --net my-net --restart=unless-stopped -d mysql:5.7.26 Tips -e MYSQL_ROOT_PASSWORD=123456命令: 指定MySQL的登录密码为 123456 -v /opt/mysql/data:/var/lib/mysql 命令: 将宿主机的目录 /opt/mysql 挂载到容器内部的目录 /var/lib/mysql,默认情况下 MySQL 将向 /opt/mysql 写入其数据文件。 -v /opt/mysql/conf:/etc/mysql命令:MySQL配置文件存放位置 -v /opt/mysql/log:/var/log/mysql命令:MySQL日志文件 --net my-net命令: 将该容器加入到 my-net 网络,连接到 my-net 网络的任何其他容器都可以访问 mysql 容器上的所有端口。 --restart=unless-stopped命令:在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器 -d命令:表示后台运行 -p 3306:3306命令:端口映射 --privileged=true命令:container内的root拥有真正的root权限 查看docker容器运行情况 1 docker ps 查看所有的容器 1 docker ps -a 启动一个已停止的容器 1 docker start 容器id 停止一个容器 1 docker stop <容器 ID> 停止的容器可以通过 docker restart 重启: 1 docker restart <容器 ID> 进入MySQL容器 1 docker exec -it mysql /bin/bash Tips -i: 交互式操作。 -t: 终端。 mysql: mysql 镜像。 /bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。 退出容器 1 exit 参考 https://www.runoob.com/docker/docker-tutorial.html https://docs.halo.run/getting-started/install/other/docker-mysql https://www.yiibai.com/docker/docker-introduction.html https://www.jianshu.com/p/68ec752f0454

2022/9/7
articleCard.readMore

vue学习笔记四-列表渲染

v-for 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template>  <ul>   <li v-for="user in userList" :key="user.id">id:{{ user.id }}  name:{{ user.name }}</li>  </ul> </template> <script> export default {   name: "list",   data() {     return {       userList: [         { id: "1", name: "张三" },         { id: "2", name: "李四" },       ],     };   }, }; </script> 维护状态 当Vue正在更新使用v-for渲染的元素列表时,它默认使用“就地更新的策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引的位置正确渲染。 为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的keyattribute 1 2 3 <li v-for="user in userList" :key="user.id">id:{{ user.id }} name:{{ user.name }}</li>

2022/9/5
articleCard.readMore

vue学习笔记三条件渲染

v-if 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <span v-if="flag">真</span> </template> <script> export default {   name: "conditional",   data() {     return {       flag: true     };   }, }; </script> v-else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template>   <button @click="flag = !flag">改变</button> <span v-if="flag">真</span> <span v-else>假</span> </template> <script> export default {   name: "conditional",   data() {     return {       flag: true     };   }, }; </script> v-else-if 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template>   <div v-if="type === 'A'">A</div>   <div v-else-if="type === 'B'">B</div>   <div v-else-if="type === 'C'">C</div>   <div v-else>不存在</div> </template> <script> export default {   name: "conditional",   data() {     return {       type:'A'     };   } }; </script> v-show 另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样: 1 <h1 v-show="flag">Hello!</h1> 不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。 v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用 v-if vs v-show v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。 v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。 总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。

2022/9/2
articleCard.readMore

vue学习笔记二模板语法

文本插值 最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号): 1 <span>Message: {{ msg }}</span> 双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新 。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template> <span> Message:{{msg}}</span> </template> <script> export default {   name:"text",   data(){     return{         msg:"消息提醒"     }   } } </script> 原始 HTML 双大括号将会将数据插值为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template>     <div>         <p>插值方式:{{rawHtml}}</p>         <p >HTML方式:<span v-html="rawHtml"></span></p>     </div> </template> <script> export default{     name:"RawHtml",     data(){         return {         rawHtml:"<a href='http://www.baidu.com'>百度</a>"     }    } } </script> Attribute 绑定 双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令: 1 <div v-bind:id="dynamicId"></div> v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。 简写 1 <div :id="dynamicId"></div> 开头为 : 的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template>     <div v-bind:id="dynamicId">         Attribute 绑定ID     </div>     <div :id="dynamicId">         Attribute 绑定ID 简写     </div> </template> <script> export default {     name: "attributeBindings",     data() {         return {             dynamicId: "10001"         }     } } </script> 使用 JavaScript 表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template>     <div>         <p>{{number +1}}</p>         <p>isok:{{ok ? 'YES':'NO' }}</p>         <p>message:{{message.split('').reverse().join('')}}</p>     </div> </template> <script> export default {     name: "JavaScript",     data() {         return {             number:10,             ok:'YES',             message:'Hello'         }     } } </script> 这些表达式会在当前活动实例的数据作用域下作为JavaScript被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。 1 2 3 4 5 6 <!-- 这是语句,不是表达式--> {{ var a = 1 }} <!--流程控制也不会生效,请使用三元表达式--> {{if(ok) { return message }}}

2022/9/2
articleCard.readMore

vue学习笔记一创建项目

创建项目 1.采用Vite 驱动的 Vue 项目 2.启动项目

2022/9/2
articleCard.readMore