月下·闲庭 - 编程
type: channel
link: https://blog.domye.top/category/note/
items: 利用GitHub Actions自动化部署Vue项目到服务器
items: vscode多端口启动Springboot项目
items: 苍穹外卖学习笔记
items: 黑马点评项目笔记
items: P2141 [NOIP2014 普及组] 珠心算测验题目分析
items: P1055 [NOIP2008 普及组] ISBN 号码题目分析
items: P1002 [NOIP2002 普及组] 过河卒题目分析
items: 爬虫练习1——获取电影天堂磁力链接

利用GitHub Actions自动化部署Vue项目到服务器
type: item
date: 2025-08-13T15:26:02+08:00
description: 前段时间我用vue写了一个个人主页，但是主页的配置总是要修改，每次都要自己构建好再上传，我就思考有没有一种方法能够自动化部署通过GitHub Actions自动化部署或许可行，我研究了一下，参考了CSDN上的文章，加上Deepseek搜索，找到了完美的解决方案配置SSH密钥密钥可以直接通过宝塔面板获取生成公钥和私钥当然，这种方法并不安全，有权限管理相关问题我们可以生成一个，方便管理ssh-keygen -t rsa -b 4096 -C "github-actions@yourdomain.com" -f ~/.ssh/github_actions_rsa -N ""参数解释：-t rsa: 指定密钥类型为RSA-b 4096: 指定密钥长度为4096位(更安全)-C: 添加注释，通常用邮箱作为标识-f: 指定密钥文件名称和路径-N "": 设置空密码，便于自动化流程这个命令会在~/.ssh/目录下生成两个文件：github_actions_rsa (私钥，必须保密)github_actions_rsa.pub (公钥，可公开)配置公钥cat ~/.ssh/github_actions_rsa.pub ##查看公钥 mkdir -p ~/.ssh echo "公钥内容" >> ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keysGitHub仓库配置设置Repository Secrets在GitHub仓库中配置以下机密变量：进入仓库 → Settings → Secrets and variables → Actions添加以下secrets：名称值说明SERVER_IP服务器IP地址如：123.456.789.0USERNAMESSH用户名一般为rootPRIVATE_KEYSSH私钥内容完整的私钥文件内容SERVER_DESTINATION部署目录路径如：/www/wwwroot/testGitHub Actions工作流配置在项目根目录创建.github/workflows/deploy.yml文件：name: Vue Deploy # 工作流名称 on: push: branches: - main # 仅当推送至 main 分支时触发部署 jobs: build-and-deploy: runs-on: ubuntu-latest # 使用 GitHub 提供的最新 Ubuntu 环境 steps: # 1. 检出代码仓库 - name: Checkout repository uses: actions/checkout@v4 # 使用官方 Action 检出代码 # 2. 安装 Node.js 环境 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20.11" # 指定 Node.js 版本,可以自选 # 3. 安装项目依赖 - name: Install dependencies run: npm install # 安装项目所需的 npm 包 # 4. 构建并压缩 dist 内容 - name: Build and Zip run: | sudo apt-get update && sudo apt-get install -y zip # 安装 zip 工具 npm run build # 执行 Vue 项目构建 cd dist # 进入 dist 目录 zip -r ../dist-content.zip ./* # 压缩 dist 内容（不包含 dist 文件夹） # 5. 上传 dist-content.zip（可选，用于调试） - name: Upload dist-content.zip uses: actions/upload-artifact@v4 with: name: dist-content # 构建产物名称 path: dist-content.zip # 构建产物路径 # 6. 通过 SCP 将 dist-content.zip 传输到服务器 - name: Transfer dist-content.zip via SCP uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SERVER_IP }} # 服务器 IP（从 GitHub Secrets 获取） username: ${{ secrets.USERNAME }} # SSH 用户名（从 GitHub Secrets 获取） key: ${{ secrets.PRIVATE_…

vscode多端口启动Springboot项目
type: item
date: 2025-07-26T20:48:00+08:00
description: 在.vscode文件中的launch.json文件中修改相关配置加入"vmArgs": "-Dserver.port=8081 -Denv=replica"如：{ "type": "java", "name": "Service-8081 (Replica)", "request": "launch", "cwd": "${workspaceFolder}", "mainClass": "com.hmdp.HmDianPingApplication", "projectName": "hm-dianping", "envFile": "${workspaceFolder}/.env", "vmArgs": "-Dserver.port=8081 -Denv=replica", "args": "" }其中 name决定名字，vmArgs中port决定端口然后打开左侧边栏的调试，选择相应的启动即可

苍穹外卖学习笔记
type: item
date: 2025-07-18T21:40:00+08:00
description: 项目介绍本项目（苍穹外卖）是专门为餐饮企业（餐厅、饭店）定制的一款软件产品，包括 系统管理后台 和 小程序端应用 两部分。其中系统管理后台主要提供给餐饮企业内部员工使用，可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护，对餐厅的各类数据进行统计，同时也可进行来单语音播报功能。小程序端主要提供给消费者使用，可以在线浏览菜品、添加购物车、下单、支付、催单等。项目架构功能架构图管理端功能员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ，数据统计，来单提醒[collapse status="false" title="功能列表"]模块描述登录/退出内部员工必须登录后,才可以访问系统管理后台员工管理管理员可以在系统后台对员工信息进行管理，包含查询、新增、编辑、禁用等功能分类管理主要对当前餐厅经营的 菜品分类 或 套餐分类 进行管理维护， 包含查询、新增、修改、删除等功能菜品管理主要维护各个分类下的菜品信息，包含查询、新增、修改、删除、启售、停售等功能套餐管理主要维护当前餐厅中的套餐信息，包含查询、新增、修改、删除、启售、停售等功能订单管理主要维护用户在移动端下的订单信息，包含查询、取消、派送、完成，以及订单报表下载等功能数据统计主要完成对餐厅的各类数据统计，如营业额、用户数量、订单等[/collapse]用户端功能微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览[collapse status="false" title="功能列表"]模块描述登录/退出用户需要通过微信授权后登录使用小程序进行点餐点餐-菜单在点餐界面需要展示出菜品分类/套餐分类, 并根据当前选择的分类加载其中的菜品信息, 供用户查询选择点餐-购物车用户选中的菜品就会加入用户的购物车, 主要包含 查询购物车、加入购物车、删除购物车、清空购物车等功能订单支付用户选完菜品/套餐后, 可以对购物车菜品进行结算支付, 这时就需要进行订单的支付个人信息在个人中心页面中会展示当前用户的基本信息, 用户可以管理收货地址, 也可以查询历史订单数据[/collapse]技术栈关于本项目的技术选型, 我们将会从 用户层、网关层、应用层、数据层 这几个方面进行介绍，主要用于展示项目中使用到的技术框架和中间件等。应用层SpringBoot： 快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发SpringMVC：SpringMVC是spring框架的一个模块，springmvc和spring无需通过中间整合层进行整合，可以无缝集成Spring Task: 由Spring提供的定时任务框架httpclient: 主要实现了对http请求的发送Spring Cache: 由Spring提供的数据缓存框JWT: 用于对应用程序上的用户进行身份验证的标记阿里云OSS: 对象存储服务，在项目中主要存储文件，如图片等Swagger： 可以自动的帮助开发人员生成接口文档，并对接口进行测试POI: 封装了对Excel表格的常用操作WebSocket: 一种通信网络协议，使客户端和服务器之间的数据交换更加简单，用于项目的来单、催单功能实现数据层MySQL： 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储Redis： 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存Mybatis： 本项目持久层将会使用Mybatis开发pagehelper: 分页插件spring data redis: 简化java代码操作Redis的API开发环境后端环境后端工程基于 maven 进行项目构建，并且进行分模块开发。整体结构对工程的每个模块作用说明：[collapse status="false" title="作用说明"]序号名称说明1sky-take-outmaven父工程，统一管理依赖版本，聚合其他子模块2sky-common子模块，存放公共类，例如：工具类、常量类、异常类等3sky-pojo子模块，存放实体类、VO、DTO等4sky-server子模块，后端服务，存放配置文件、Controller、Service、Mapper等[/collapse]sky-common模块中存放的是一些公共类，可以供其他模块使用分析sky-common模块的每个包的作用：[collapse status="false" title="作用说明"]名称说明constant存放相关常量类context存放上下文类enumeration项目的枚举类存储exception存放自定义异常类json处理json转换的类propert…

黑马点评项目笔记
type: item
date: 2025-07-15T20:43:00+08:00
description: 实战篇Redis基于Redis实现短信登录流程分析生成验证码接受前端发送的数据，后端进入验证码发送流程检验手机格式若正确，随机生成验证码将验证码存于Redis中登录用户填入验证码后发送请求，后端进入登录流程检验手机号格式利用手机号从Redis获取相关联的验证码检验验证码是否和用户输入的验证码是否相同相同，则查询是否存在账户，若无，则创建一个新用户若有，则将用户的信息封装为DTO存入Redis中生成token,用于验证用户返回token给前端拦截器获取请求头中的tokenb基于token获取Redis中的用户刷新token有效期发送短信验证码@Override public Result sendCode(String phone, HttpSession session) { // 1.校验手机号格式 if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("手机号格式错误"); } // 2.生成验证码 String code = RandomUtil.randomNumbers(6); // 3.将验证码存入Redis stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); // 4.发送验证码 log.debug("发送短信验证码成功，手机号：{}，验证码：{}", phone, code); return Result.ok("验证码已发送"); }检验手机号格式，调用RegexUtils下的isPhoneInvalid函数，对应代码为/** * 是否是无效手机格式 * @param phone 要校验的手机号 * @return true:符合，false：不符合 */ public static boolean isPhoneInvalid(String phone){ return mismatch(phone, RegexPatterns.PHONE_REGEX); } // 校验是否不符合正则格式 private static boolean mismatch(String str, String regex){ if (StrUtil.isBlank(str)) { return true; } return !str.matches(regex); }RegexPatterns.PHONE_REGEX为对应的正则表达式，mismatch函数先判断该字符串是否为空，若为空，则不符合正则格式，返回truematches是 Java 中 String 类的一个方法，用于判断字符串是否与给定的正则表达式模式匹配发送业务功能暂未实现，先模拟输出log短信验证码登录从Redis获取验证码并检验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); String code = loginForm.getCode(); if (cacheCode == null || !cacheCode.toString().equals(code)) { return Result.fail("验证码错误"); }调用stringRedisTemplate中的opsForValue()获取验证码，若不一致，则报错存储获取到的用户信息User user = query().eq("phone", phone).one(); // 不存在，创造新用户并保存 if (user == null) { user = createUserWithPhone(phone); } // 将User对象转为HashMap存储 UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create() .setIgnoreNullValue(true) .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));因为stringRedisTemplate我们所用的为其中对String类型操作的函数,而DTO变量类型不统一，直接传递会报错，所以需要先转换BeanUtil.beanToMap() 将Java Bean对象…

P2141 [NOIP2014 普及组] 珠心算测验题目分析
type: item
date: 2024-11-20T09:46:00+08:00
description: 题目攻克点如何确定数组中两数相加的和是否存在于这个数组中如何求出遍历循环的范围由说明可知，两组数若相加起来答案相同，只记一次，需要一个方法去重如何计数原理确定数组中两数相加的和是否存在于这个数组中建立一个数组，用for循环输入所需数据再建立一个布尔型数组，利用数组嵌套，使这个数组中该数数值与其位置相绑定，例如a[5]=3,定义g[a[5]]=1,即表示g[3]=1,即数组中存在3这个数字核心就是用数组的个数来代表其数值求出遍历循环的范围求所有可能的两个数相加的结果出现的次数，而这个结果的范围是从最小的两数之和到最大的两数之和所以只需要求出最大值即可，循环范围小于最大值求和并去重再定义一个数组，计算数组中两数相加，即利用循环计算a[i] + a[j]，然后利用数组嵌套，如t[a[i] + a[j]]，每计算一次，将其结果+1例如a[1]=3, a[2]=4,t[a[1]+a[2]]=t[7] ，t[7]++a[3]=4,a[2]=4, t[a[1]+a[2]]=t[7]，t[7]++定义数组中初始都为0，两个数相加结果为x，则t[x]必>=1如何计数由上可知，只需t[i] > 0 && g[i]==1，即表示其和存在，定义变量，遍历数组t，每符合一次上述情况，结果加一在t[i]中的i，只会出现一次，且重复的次数会在t[i]的值中体现，所以能达到去重的效果代码分析确定数组中两数相加的和是否存在于这个数组中，利用循环和数组嵌套for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); g[a[i]] = 1; }即如果g[x]=1，则x存在于原数组a中定义maxn，利用循环遍历找出两数相加的最大值maxn = 0; // 初始化 maxn for (int i = 1; i < n; i++) { // 暴力枚举求出可以加出的数 for (int j = i + 1; j <= n; j++) { if (a[i] + a[j] > maxn) { maxn = a[i] + a[j]; // 求出数中最大值 } } }建立数组t，存储a[i] + a[j]值出现的次数for (int i = 1; i < n; i++) { // 暴力枚举求出可以加出的数 for (int j = i + 1; j <= n; j++) { t[a[i] + a[j]]++; // a[i]+a[j]这个数被加出来了 } }定义变量，用来存储重复的次数ans = 0; // 初始化 ans for (int i = 1; i <= maxn; i++) { // 只需要枚举到最大值即可 if (t[i] > 0 && g[i]) ans++; // 判断是否满足，满足就 ans++ }打印结果printf("%d\n", ans);完整代码#include <stdio.h> #include <string.h> #include <stdlib.h> const int M = 20005; // 20005由于最大值是10000+10000=20000，const int是静态定义，定义后该值（即M)无法修改。 int t[M], g[M]; // t是桶，t[i]表示值为i的数在集合中两两相加出现了几次，g[i]表示值为i的数是否在原集合中，1为在，0为不在 int n, a[105], ans, maxn; // a数组开105是由于总共最多100个数 int main() { scanf("%d", &n); memset(g, 0, sizeof(g)); // 初始化 g 数组 for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); // 读入 g[a[i]] = 1; // 在集合中赋值为1 } memset(t, 0, sizeof(t)); // 初始化 t 数组 maxn = 0; // 初始化 maxn for (int i = 1; i < n; i++) { // 暴力枚举求出可以加出的数 for (int j = i + 1; j <= n; j++) { t[a[i] + a[j]]++; // a[i]+a[j]这个数被加出来了 if (a[i] + a[j] > maxn) { maxn = a[i] + a[j]; // 求出数中最大值 } } } ans = 0; // 初始化 ans for (int i = 1; i <= maxn; i++) { // 只需要枚举到最大值即可 if (t[i] > 0 && g[i]) ans++; // 判断是否满足，满足就 ans++ } printf("%d\n", ans…

P1055 [NOIP2008 普及组] ISBN 号码题目分析
type: item
date: 2024-11-18T17:31:00+08:00
description: 题目攻克点如何将字符转化为数字如何将10转化为X如何判断数组中各数字加起来是否等于末尾原理将字符转化为数字实现字符转化为数字的关键在于字符的ASCII值（或字符编码）与数字的ASCII值之间的关系。在计算机内部，每一个字符都有对应的数字编码（ASCII），例如：字符 '0' 的ASCII值是 48字符 '1' 的ASCII值是 49字符 '2' 的ASCII值是 50依此类推，到字符 '9' 的ASCII值是 57可通过执行 减去 '0' ，即做两个字符的ASCII值之间的减法操作，例如：字符 '3'，它的ASCII值是 51。'0' 的ASCII值是 48。所以，'3' - '0' 计算过程是：51 - 48 = 3。这种方式可以方便地将字符表示的数字转化为整数，以便进行数学运算将10转化为X可定义一个数组，第十个即为X判断数组中各数字加起来是否等于末尾将编码存储于数组中for循环，遇到'-'即跳过若不跳过，将各个字符转化为数字后与对应位置数字相乘后加起来代码分析定义两个数组，一个用来存储号码，另一个用来存储识别码char a[14], mod[12] = "0123456789X"; scanf("%s", a);在C / C++ 中以字符串末尾有不可见 '\0', 所以开数组存储字符的时候一定要多 + 1位留给计算机自动补 '\0';定义for循环中的变量，并用for循坏计算各个数经过处理后的总和，遇到'-'跳过int i, j = 1, t = 0; for (i = 0; i < 12; i++) { if (a[i] == '-') continue; t += (a[i] - '0') * j++; }判断识别码是否正觉，若不正确，更改if (mod[t % 11] == a[12]) { printf("Right\n"); } else { a[12] = mod[t % 11]; printf("%s\n", a); }完整代码#include <stdio.h> int main() { char a[14], mod[12] = "0123456789X"; scanf("%s", a); int i, j = 1, t = 0; for (i = 0; i < 12; i++) { if (a[i] == '-') continue; t += (a[i] - '0') * j++; } if (mod[t % 11] == a[12]) { printf("Right\n"); } else { a[12] = mod[t % 11]; printf("%s\n", a); } return 0; }

P1002 [NOIP2002 普及组] 过河卒题目分析
type: item
date: 2024-11-17T22:20:00+08:00
description: 题目攻克点计算路线条数的方法定义马可在地方的方法原理计算路线条数的方法先考虑如果没有任何马的限制，卒子可以随便向右向下走那么可以想到，一个卒子只能从 当前格子的左侧格子 和 当前格子的上方格子 上走到当前格子那么假设从 (1,1)走到 当前格子的左侧格子 的路径条数是x，从 (1,1)走到 当前格子的上方格子 的路径条数是 y那么从 (1,1) 走到当前格子的路径条数就应该是 x+y定义马可在地方的方法定义数组，然后将马的横纵坐标分别减去坐标里面的数字实现定义初始位置为1，向右或向右移动时，数字为左边和上面的数字之和，最后终点的数字即为路线条数绿色为马能阻拦的地方，遇到这种地方时跳过，且不计数代码分析定义马可以行动的横纵坐标，用数组一一对应const int fx[] = {0, -2, -1, 1, 2, 2, 1, -1, -2}; const int fy[] = {0, 1, 2, 2, 1, -1, -2, -2, -1};定义所需要的变量，并输入long long f[40][40];//定义棋盘 int bx, by, mx, my; scanf("%d%d%d%d", &bx, &by, &mx, &my); f[0][0] = 1; // 初始化卒的位置 s[mx][my] = 1; // 初始化马的位置定义布尔型数组，用来存储马能行动的地方，且定义为1，方便后面去除s[mx][my] = 1; // 标记马的位置 for (int i = 1; i <= 8; i++) s[mx + fx[i]][my + fy[i]] = 1;用for循环遍历x轴，并嵌套循环y轴，遇到马能抵达的地方时跳过for (int i = 0; i <= bx; i++) { for (int j = 0; j <= by; j++) { if (s[i][j] == 1)//根据步骤3中的定义，如果马能到达，则其值为1 continue; // 如果被马能到达时就跳过，到下一个格子 if (i > 0) f[i][j] += f[i - 1][j]; // 防止访问负数索引 if (j > 0) f[i][j] += f[i][j - 1]; // 防止访问负数索引 } }打印终点格子的值printf("%lld\n", f[bx][by]);完整代码#include <iostream> int main() { const int fx[] = {0, -2, -1, 1, 2, 2, 1, -1, -2}; const int fy[] = {0, 1, 2, 2, 1, -1, -2, -2, -1}; int bx, by, mx, my; long long f[40][40]; bool s[40][40]; scanf("%d%d%d%d", &bx, &by, &mx, &my); f[0][0] = 1; s[mx][my] = 1; for (int i = 1; i <= 8; i++) s[mx + fx[i]][my + fy[i]] = 1; for (int i = 0; i <= bx; i++) { for (int j = 0; j <= by; j++) { if (s[i][j] == 1) continue; if (i > 0) f[i][j] += f[i - 1][j]; if (j > 0) f[i][j] += f[i][j - 1]; } } printf("%lld\n", f[bx][by]); return 0; }

爬虫练习1——获取电影天堂磁力链接
type: item
date: 2024-11-07T17:23:00+08:00
description: 目的爬取盗版电影天堂设置分类，获取指定类型的电影下载链接流程获取主页源码筛选主页源码中各个类型电影的榜单获取各个榜单中电影的详情页链接获取各个详情页的源码筛选详情页源码，获取需要的下载链接，并输出分析代码1. 获取网页url= "https://www.dytt89.com" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"} ##设置UA，防止反爬 resp=requests.get(url,*headers*=headers) ##通过request.get获取网页源码 resp.encoding = "gbk" ##根据网站编码类型调整2. 摘取需要的内容利用re模块，编写相应规则使用re.compile编写规则如obj=re.compile(r' .*?href=\'(?P .*?)\'', re.S)re.S 是 Python 中 re 模块中的一个标志，它用于改变正则表达式的行为，使得点号（.）可以匹配包括换行符在内的所有字符3. 获取榜单子页面链接用re.finditer获取所有内容obj1=re.compile(rf'{re.escape(movie_type)}.*? (?P<bangdan>.*?) ', re.S) result=obj1.finditer(resp.text)其中定义所需要获取的内容为bangdan利用{re.escape()}来引入变量用循环输出所需要的内容for i in result: ul=i.group("bangdan")初始化一个空列表，用于存储链接quanlianjie=[]同步骤一的方法，获取每个榜单的链接obj2=re.compile(r' .*?href=\'(?P .*?)\'', re.S) result2=obj2.finditer(ul) for a in result2: lianjie=url+a.group("link") quanlianjie.append(lianjie)其中，利用append将合成好的链接放于列表中，方便下一次循环使用4. 创建文件存储输出结果with open(f'{movie_type}.txt', 'w', encoding='utf-8') as f以写入模式（'w'）打开一个名为 movie_type.txt 的文件，其中 movie_type 是一个变量，当执行时会被替换为其值encoding='utf-8'` 指定了文件的编码格式为 UTF-8这行代码的上下文是将从电影详情页中提取的磁力链接写入到该文件中5. 同上流程获取子页面源码，并筛选for i in quanlianjie: resp2=requests.get(i,headers=headers) resp2.encoding = "gbk" obj3=re.compile(r' (?P<magnet>.*?)', re.S) result3=obj3.finditer(resp2.text) for j in result3: magnet=j.group("magnet")6. 写入文件f.write(magnet + '\n')