月下·闲庭 - Domye
type: channel
link: https://blog.domye.top/author/1/
description: Domye
items: 基于自定义注解与AOP的精细化权限控制
items: Spring Boot万能脚手架：5分钟搭建企业级开发模板
items: 解决Vue History模式服务器端Nginx的配置问题
items: 利用GitHub Actions自动化部署Vue项目到服务器
items: Vue中的路由与Vuex的使用
items: Vue工程化开发及组件通信
items: Vue入门指令及属性
items: 数据结构笔记1——线性表
items: Redisson实现分布式锁
items: vscode多端口启动Springboot项目

基于自定义注解与AOP的精细化权限控制
type: item
date: 2025-09-26T19:22:36+08:00
description: 🔐 基于自定义注解与AOP的精细化权限控制🌟 前言：权限控制的演进之路在分布式系统开发中，接口权限控制是保障系统安全的重要环节。传统基于数据库字段查询的权限控制方案存在几个明显缺陷：性能瓶颈：每次请求都需要查询数据库获取角色信息代码冗余：相同的校验逻辑分散在各个Controller中维护困难：权限规则变更需要修改多处业务代码以用户管理系统为例，我们需要区分管理员(admin)和普通用户(user)的权限。传统实现方式如下：// 传统实现方式的典型代码 @GetMapping("/admin/users") public List<User> getAllUsers(HttpServletRequest request) { // 每次都需要查询数据库校验权限 User loginUser = userService.getLoginUser(request); if (!"admin".equals(loginUser.getUserRole())) { throw new RuntimeException("无权限访问"); } return userService.list(); }本文将介绍如何通过自定义注解+Spring AOP实现声明式的权限控制，让权限校验逻辑与业务代码完美解耦。🧩 核心组件设计1. 自定义注解设计原理Java注解本质上是接口的扩展，通过@interface关键字定义。我们设计的@AuthCheck注解包含以下关键特性：@Target(ElementType.METHOD)：限定注解只能用于方法@Retention(RetentionPolicy.RUNTIME)：确保注解在运行时可通过反射获取mustRole属性：定义方法访问所需的最小权限@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthCheck { /** * 访问该方法必须拥有的角色 * @default "" 表示不需要特定角色 */ String mustRole() default ""; }2. AOP拦截器实现机制Spring AOP通过动态代理实现方法拦截。我们的AuthInterceptor核心功能包括：通过@Around注解定义切入点获取方法上的@AuthCheck注解配置从请求上下文获取当前用户执行角色权限校验决定是否放行或拦截@Aspect @Component public class AuthInterceptor { @Resource private UserService userService; @Around("@annotation(authCheck)") public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable { // 获取注解配置的必须角色 String mustRole = authCheck.mustRole(); // 获取当前请求对象 RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); // 获取当前登录用户 User loginUser = userService.getLoginUser(request); // 角色枚举转换 UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); // 无需特定角色则直接放行 if (mustRoleEnum == null) { return joinPoint.proceed(); } // 获取用户角色 UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); // 角色校验失败处理 if (userRoleEnum == null) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 管理员权限特殊校验 if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equa…

Spring Boot万能脚手架：5分钟搭建企业级开发模板
type: item
date: 2025-08-25T15:28:00+08:00
description: 为什么需要个人通用模板？提高项目初始化效率避免每次新建项目时重复配置基础依赖统一技术栈版本，减少兼容性问题快速集成常用组件（如MyBatis Plus等）统一项目规范预置代码风格配置统一的日志格式和收集方案标准化异常处理机制积累最佳实践封装经过验证的工具类沉淀业务通用解决方案记录踩坑经验与解决方案新建项目现在使用IDEA新建项目十分简单，直接左上角新建项目即可对于jdk版本，开发的要求当然是越稳定越好，所以我们可以选择11或者17但是注意，官方的Server Url不支持选择Java8和Java11，所以我们可以将上方的uel设置为https://start.aliyun.com/同样是因为稳定性需求，我们可以选择Springboot2.7.6版本，点击下一步，然后在此页面我们可以加入一些自己常用的依赖Spring Web、MySQL、Lombok自然是少不了,此时勾选即可选择完后直接创建即可添加依赖MyBatis-PlusMyBatis-Plus 是一个 MyBatis 的增强工具，在 MyBatis 的基础上只做增强不做改变，为简化开发、提高效率而生在使用 MyBatis-Plus 时，通常不需要再单独引入 MyBatis 的依赖，因为 MyBatis-Plus 本身已经内置了 MyBatis 的核心依赖（即 mybatis 和 mybatis-spring），它会自动传递依赖进来具体可以看文档：https://baomidou.com/在Maven中的pom.xml中添加依赖<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.12</version> </dependency>添加完以来后我们需要配置一下，在resource文件夹下删除原来的配置文件，新建一个application.yml文件# Spring框架配置 # 配置Spring应用的基本信息和数据源连接 spring: # 应用配置 application: # 应用名称，用于服务注册与发现 name: 你的项目名字 # 数据源配置 datasource: # MySQL数据库驱动类名 driver-class-name: com.mysql.cj.jdbc.Driver # 数据库连接URL url: jdbc:mysql://localhost:3306/dmoye_picture # 数据库用户名 username: root # 数据库密码 password: xxxxx # MyBatis-Plus配置 # 配置MyBatis-Plus的映射规则和全局设置 mybatis-plus: # MyBatis配置项 configuration: #MyBatis配置 # 是否开启下划线到驼峰命名的自动转换 map-underscore-to-camel-case: false #仅在开发环境使用 # SQL执行日志实现类，用于控制台输出SQL语句 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # MyBatis-Plus全局配置 global-config: # 数据库配置 db-config: # 逻辑删除字段名 logic-delete-field: isDelete # 逻辑已删除值 logic-delete-value: 1 # 逻辑未删除值 logic-not-delete-value: 0我们配置好后，可以启动测试，发现项目可以正常启动Hutool工具库Hutool是一个 Java 工具类库，它封装了 Java 开发中常用的功能，提供了简单易用的 API，能够显著减少代码量，提高开发效率。Hutool 的目标是成为 Java 界的 "Swiss Army Knife"（瑞士军刀），涵盖工具方法、IO、加密、HTTP、缓存、JSON、Excel 等众多实用模块官方文档：https://doc.hutool.cn/pages/index在Maven中的pom.xml中添加依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.38</version> </dependency>Knife4j 接口文档Knife4j是一个为Java MVC框架集成Swagger生成API文档的增强解决方案，它集成了Swagger2和OpenAPI3规范，提供了一系列强大的文档展示和调试功能，让我们能够快速的调试我们…

解决Vue History模式服务器端Nginx的配置问题
type: item
date: 2025-08-14T20:48:00+08:00
description: Vue Router 的 history 模式 使用的是 纯前端路由，即 URL 路径（如 /about）并不对应服务器上的真实文件或目录，而是由 JavaScript 动态渲染内容而传统 后端路由（如 PHP、Java）的 /about 可能对应一个真实的 about.php 或 about.html 文件当用户直接访问或刷新页面时：浏览器会向服务器请求 /about但服务器上并没有 about 这个文件或接口Nginx 默认返回 404（因为找不到匹配的资源）因此需要配置nginxlocation / { try_files $uri $uri/ /index.html; }它的执行逻辑是：$uri → 先检查请求的路径是否对应真实文件（如 /about → /about.html 或 /about/index.html）如果存在，直接返回该文件如果不存在，进入下一步$uri/ → 检查请求的路径是否对应一个目录（如 /about/）如果存在，尝试返回该目录下的默认文件（如 index.html）如果不存在，进入下一步/index.html → 如果前两步都失败，最终返回 index.htmlVue 会解析 URL 路径（如 /about），并渲染对应的前端页面这样，无论是直接访问 /about 还是刷新页面，Nginx 都会返回 index.html，由 Vue Router 处理路由逻辑。

利用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_…

Vue中的路由与Vuex的使用
type: item
date: 2025-08-09T16:59:00+08:00
description: 路由简单使用安装固定5个固定的步骤（不用死背，熟能生巧）下载 VueRouter 模块到当前工程yarn add vue-router@3.6.5在main.js中import VueRouter from 'vue-router'//引入VueRouter Vue.use(VueRouter)//安装注册 const router = new VueRouter()//创建路由对象 new Vue({ render: h => h(App), router:router//将路由对象注入到new Vue实例中 }).$mount('#app')当我们配置完以上5步之后 就可以看到浏览器地址栏中的路由 变成了 /#/的形式。表示项目的路由已经被Vue-Router管理了配置创建好需要的组件 (views目录)，配置路由规则const router = new VueRouter({ // routes 路由规则们 // route 一条路由规则 { path: 路径, component: 组件 } routes: [ { path: '/find', component: Find }, { path: '/my', component: My }, { path: '/friend', component: Friend }, ] }) export default router每条路由规则是一个对象，包含：path：URL 路径（如 /find）component：路径匹配时渲染的组件（需提前导入组件如 Find）而export default router将配置好的路由实例导出，供 Vue 主文件（如 main.js）注入到根实例中import Vue from 'vue' import App from './App.vue' import router from './router/index' new Vue({ render: h => h(App), router }).$mount('#app')声明式导航router-link标签vue-router 提供了一个全局组件 router-link (取代 a 标签)能跳转，配置 to 属性指定路径(必须) 。本质还是 a 标签 ，to 无需 #能高亮，默认就会提供高亮类名，可以直接设置高亮样式语法： <router-link to="path的值">发现音乐</router-link> <router-link to="/find">发现音乐</router-link> <router-link to="/my">我的音乐</router-link> <router-link to="/friend">朋友</router-link> <router-view></router-view> router-link类名使用router-link跳转后，我们发现。当前点击的链接默认加了两个class的值 router-link-exact-active和router-link-active在 Vue Router 中，router-link-active 和 router-link-exact-active 是两个非常重要的 CSS 类名，它们由 Vue Router 自动添加到 <router-link> 组件上，用于指示当前导航链接的激活状态：router-link-active触发条件：当路由路径部分匹配时添加匹配规则：只要当前路由的路径包含该 <router-link> 的目标路径，就会添加示例： html <router-link to="/user">用户</router-link> 当访问 /user → 激活 - 当访问 /user/profile → 激活（因为包含 /user）当访问 /product → 不激活router-link-exact-active触发条件：当路由路径完全匹配时添加匹配规则：只有当当前路由的路径完全等于该 <router-link> 的目标路径时才会添加示例： html <router-link to="/user">用户</router-link> 当访问 /user → 激活 - 当访问 /user/profile → 不激活（路径不完全相等）当访问 /users → 不激活（即使相似但不相等）自定义类名router-link的两个高亮类名太长了，我们可以在创建路由对象时，额外配置两个配置项const router = new VueRouter({ linkActiveClass: 'active', // 配置模糊匹配的类名 linkExactActiveClass: 'exact-active' // 配…

Vue工程化开发及组件通信
type: item
date: 2025-08-07T18:13:00+08:00
description: 工程化开发和脚手架开发Vue的两种方式核心包传统开发模式：基于html / css / js 文件，直接引入核心包，开发 Vue。工程化开发模式：基于构建工具（例如：webpack）的环境中开发Vue。工程化开发模式优点：提高编码效率，比如使用JS新语法、Less/Sass、Typescript等通过webpack都可以编译成浏览器识别的ES3/ES5/CSS等工程化开发模式问题：webpack配置不简单雷同的基础配置缺乏统一的标准为了解决以上问题，所以我们需要一个工具，生成标准化的配置脚手架Vue CLIVue CLI 是Vue官方提供的一个全局命令工具可以帮助我们快速创建一个开发Vue项目的标准化基础架子全局安装（只需安装一次即可） yarn global add @vue/cli 或者 npm i @vue/cli -g查看vue/cli版本： vue --version创建项目架子：vue create project-name(项目名不能使用中文)启动项目：yarn serve 或者 npm run serve(命令不固定，找package.json)项目目录介绍和运行流程项目目录介绍虽然脚手架中的文件有很多，目前咱们只需认识三个文件即可main.js 入口文件App.vue App根组件index.html 模板文件运行流程根组件根组件介绍整个应用最上层的组件，包裹所有普通小组件组件是由三部分构成template：结构 （有且只能一个根元素）script: js逻辑style： 样式 (可支持less，需要装包)组件注册局部注册局部注册的组件只能在注册的组件内使用创建.vue文件（三个组成部分）在使用的组件内先导入再注册，最后使用当成html标签使用即可 <组件名></组件名>组件名规范 —> 大驼峰命名法， 如 HmHeader// 导入需要注册的组件 import 组件对象 from '.vue文件路径' import HmHeader from './components/HmHeader' export default { // 局部注册 components: { '组件名': 组件对象, HmHeader:HmHeaer, HmHeader } }全局注册全局注册的组件，在项目的任何组件中都能使用创建.vue组件（三个组成部分）main.js中进行全局注册// 导入需要全局注册的组件 import HmButton from './components/HmButton' Vue.component('HmButton', HmButton)局部样式写在组件中的样式会 全局生效 ，因此很容易造成多个组件之间的样式冲突问题。可以给组件的style标签加上scoped 属性,可以让样式只作用于当前组件即 添加后，当前组件内标签都被添加data-v-hash值 的属性 ，css选择器都被添加 [data-v-hash值] 的属性选择器因此必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到 组件通信组件通信，就是指组件与组件之间的数据传递组件的数据是独立的，无法直接访问其他组件的数据。想使用其他组件的数据，就需要组件通信父子通信父组件通过 props 将数据传递给子组件子组件利用 $emit 通知父组件修改更新父向子通信父组件 我是APP组件 <Son :title="myTitle"></Son> 子组件 我是Son组件 {{title}} 传值步骤给子组件以添加属性的方式传值子组件内部通过props接收模板中直接使用 props接收的值子向父通信子组件利用 $emit 通知父组件，进行修改更新父组件 我是APP组件 <Son :title="myTitle" @changTitle="handleChange"></Son> 传值步骤$emit触发事件，给父组件发送消息通知父组件监听$emit触发的事件提供处理函数，在函数的性参中获取传过来的参数Propsprops 是组件间数据传递的核心机制，用于父组件向子组件传递数据单向数据流 Props 数据只能从父组件流向子组件，子组件不能直接修改 props（会触发警告）声明式接收 子组件需显式声明它接受的 props动态/静态传递 父组件可传递静态值或动态绑定的数据Props 的声明方式数组形式（简单声明）export default { props: ['title', 'content', 'isPublished'] }对象形式export default { props: { title: String, // 基础类型检查 // 高级配置 content: { type: String, // 数据类型 required: true,…

Vue入门指令及属性
type: item
date: 2025-08-07T10:31:00+08:00
description: 内容渲染指令内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下2 个：v-text（类似innerText）使用语法： hello ，意思是将 uame 值渲染到 p 标签中类似 innerText，使用该语法，会覆盖 p 标签原有内容v-html（类似 innerHTML）使用语法： hello ，意思是将 intro 值渲染到 p 标签中类似 innerHTML，使用该语法，会覆盖 p 标签原有内容类似 innerHTML，使用该语法，能够将HTML标签的样式呈现出来 个人信息 姓名： 简介： 条件渲染指令条件判断指令，用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个，分别是：v-show作用： 控制元素显示隐藏语法： v-show = "表达式" 表达式值为 true 显示， false 隐藏原理： 切换 display:none 控制显示隐藏场景：频繁切换显示隐藏的场景v-if作用： 控制元素显示隐藏（条件渲染）语法： v-if= "表达式" 表达式值 true显示， false 隐藏原理： 基于条件判断，是否创建 或 移除元素节点场景： 要么显示，要么隐藏，不频繁切换的场景v-else 和 v-else-if作用：辅助v-if进行判断渲染语法：v-else v-else-if="表达式"需要紧接着v-if使用事件绑定指令使用Vue时，如需为DOM注册事件，及其的简单，语法如下： 按钮 按钮 按钮 v-on:可简写为 @ - + + 属性绑定指令作用：动态设置html的标签属性 比如：src、url、title语法：v-bind:属性名=“表达式”v-bind:可以简写成 => :比如，有一个图片，它的 src 属性值，是一个图片地址。这个地址在数据 data 中存储。则可以这样设置属性值： 控制样式为了方便开发者进行样式控制， Vue 扩展了 v-bind 的语法，可以针对 class 类名 和 style 行内样式 进行控制 。 :class = "对象/数组">这是一个div 当class动态绑定的是对象时，键就是类名，值就是布尔值，如果值是true，就有这个类，否则没有这个类, 适用一个类名，来回切换 当class动态绑定的是数组时 → 数组中所有的类，都会添加到盒子上，本质就是一个 class 列表，可用于批量添加或删除类 对有样式控制的增强 {{ percent }}% 设置25% 设置50% 设置75% 设置100% 列表渲染指令Vue 提供了 v-for 列表渲染指令，用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使用 (item, index) in arr 形式的特殊语法，其中：item 是数组中的每一项index 是每一项的索引，不需要可以省略arr 是被遍历的数组 {{ item.name }} {{ item.author }} 删除 给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。Vue 的默认行为会尝试原地修改元素（就地复用）,加key可避免key 的值只能是字符串 或 数字类型，且值必须具有唯一性双向绑定指令数据改变后，呈现的页面结果会更新页面结果更新后，数据也会随之而变作用： 给表单元素（input、radio、select）使用，双向绑定数据，可以快速 获取 或 设置 表单元素内容语法：v-model="变量" 账户： 密码： 登录 重置 指令修饰按键修饰符@keyup.enter —>当点击enter键的时候才触发v-model修饰符v-model.trim —>去除首位空格v-model.number —>转数字事件修饰符@事件名.stop —> 阻止冒泡@事件名.prevent —>阻止默认行为@事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为ref和$refs在 Vue.js 2 中，ref 和 $refs 是用于直接访问 DOM 元素或子组件实例的特殊属性。它们提供了一种脱离响应式系统的直接操作方式，适用于需要手动控制 DOM 或调用子组件方法的场景。ref 属性作用：在模板中标记一个 DOM 元素或子组件，Vue 会将其引用注册到 $refs 对象中。语法： Hello <child-component ref="myChild"></child-component>$refs作用：通过 this.$refs 访问所有被 ref 标记的元素或组件实例。特性：非响应式（修改 ref 不会触发视图更新）。仅在组件渲染完成后填充（避免在 created 生命周期中访问）。访问 DOM 元素 聚焦输入框 调用子组件方法 <child-component ref="ch…

数据结构笔记1——线性表
type: item
date: 2025-08-01T19:39:00+08:00
description: 顺序表 用一组连续的内存单元依次存储线性表的各个元素，也就是说，逻辑上相邻 的元素，实际的物理存储空间也是连续的。存储结构#define MAXSIZE 20 //线性表存储空间的初始分配量 #define OK 1 //成功标识 #define ERROR 0 //失败标识 typedef int Status; //Status是函数的类型，其值是函数结果状态代码，如OK等 typedef int ElemType; //ElemType的类型根据实际情况而定，这里假定为int typedef struct { ElemType *elem; //存储 int length; }SqList;即其中maxsize为线性表存储空间的初始值初始化Status InitList(SqList* L){ //构造一个空的线性表L L -> elem = (ElemType *)malloc(MAXSIZE*sizeof(ElemType)); if(!L -> elem){ // 如果分配失败 return ERROR; } L -> length = 0; return OK; }插入Status ListInsert(SqList *L, int i, ElemType e){ int k; if (L->length == MAXSIZE){ //线性表已满 return ERROR; } if (i < 1 || i > L->length+1){ //当i不在范围内时 return ERROR; } if (i <= L->length){ //若插入位置不在表尾 for(k = L->length-1;k >= i-1;k--){ L->elem[k+1] = L->elem[k]; } } L->elem[i-1] = e; //将新元素插入 L->length++; //长度加1 return OK; }插入，即将原来的元素都向后移动一位删除元素同理，删除元素即所有元素向左移动一位Status ListDelete(SqList *L, int i, ElemType *e){ if(L->length == 0){ //线性表为空 return ERROR; } if(i < 1 || i > L->length){ //删除位置不正确 return ERROR; } *e = L -> elem[i-1]; if(i < L->length){ //如果删除位置不在最后位置 for(int k; = i;k < L->length;k++){ L->elem[k-1] = L->elem[k]; } } L->length--; //长度减1 return OK; }*e的作用：保存被删除的元素值查找Status GetElem(SqList L, int i, ElemType *e){ if(L.length == 0 || i<1 || i>L.length){ return ERROR; } *e = L.elem[i-1]; return OK; }读取所有元素void OutPut(SqList L){ printf("当前顺序表的长度:%d\n", L.length); for(int i = 0; i < L.length; i++){ printf("%d ",L.elem[i]); } printf("\n"); }小结线性表的顺序存储结果在读、存数据是的时间复杂度是O(1)，插入、删除操作的时间复杂度是O(n)无须为表中元素之间的逻辑关系而增加额外的存储空间；可以快速的存取表中任一位置的元素但是当插入和删除操作需要移动大量元素时，若线性表长度较大，难以确定存储空间的容量；造成存储空间的“碎片”单链表概念在链式结构中，除了要存储数据元素的信息外，还要存储它的后继元素的存储地址为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系，对数据ai来说除了存储其本身的信息之外，还需要存储一个指示其直接后继的信息（即直接后继的存储位置）我们把存储数据元素信息的域称为数据域，把存储直接后继位置的域称为指针域指针域中存储的信息称做指针或链这两部分信息组成数据元素ai的存储映像，称为结点（Node）n个结点（ai的存储映像）链结成一个链表，即为线性表（a1, a2, …, an）的链式存储结构因为此链表的每个结点中只包含一个指针域，所以叫做单链表存储结构typedef int ElemType; typedef struct node{ ElemType data; struct node *next; }Node初始化Node* initList() { Node *head = (Node*)m…

Redisson实现分布式锁
type: item
date: 2025-07-30T20:51:00+08:00
description: 分布式锁分布式锁是用于协调分布式系统中多个进程服务互斥访问共享资源的机制。其核心目标是确保在分布式环境下，同一时刻只有一个客户端能操作共享资源（如数据库、文件等），以解决并发导致的数据不一致问题 。核心特性互斥性：同一时刻仅有一个客户端持有锁，其他请求需等待或失败 。超时机制：锁自动过期释放，避免因客户端故障导致死锁（如 Redis 的 PX 参数） 。可重入性：同一线程可重复获取同一把锁（如 Redisson 通过 Hash 结构记录线程标识和重入次数） 。高可用与容错：锁服务需支持集群部署，避免单点故障（如 Redis 的 RedLock 算法、ZooKeeper 集群） 。常见实现方式基于数据库唯一约束：通过插入唯一键（如业务单号）实现加锁，删除记录释放锁 。悲观锁：使用 SELECT ... FOR UPDATE 锁定数据行 。缺点：性能低、死锁风险高，仅适用于低并发场景 。基于 RedisSETNX + EXPIRE：SET key value NX PX 3000 原子性加锁，Lua 脚本保证解锁原子性 。Redisson 框架：支持可重入锁、看门狗自动续期（默认每 10 秒重置超时）、订阅机制实现阻塞等待 。缺点：主从切换时可能丢锁（RedLock 算法通过多节点投票解决） 。基于 ZooKeeper临时顺序节点：客户端创建临时顺序节点，仅最小序号节点获锁；其他节点监听前序节点释放事件 。Curator 框架：封装 InterProcessMutex 实现可重入锁，节点断开自动删除临时节点 。缺点：性能低于 Redis，但强一致性和顺序性更可靠 。简易锁以Spring Data Redis为例，用RedisTemplate来操作Redis// 加锁 public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) { return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit); } // 解锁，防止删错别人的锁，以uuid为value校验是否自己的锁 public void unlock(String lockName, String uuid) { if(uuid.equals(redisTemplate.opsForValue().get(lockName)) { redisTemplate.opsForValue().del(lockName); } }这是一个简易的锁，但是加锁和删锁不是原子性的，并发一旦大了，无法保证线程安全线程A执行get操作，获取到锁的值"uuidA"，判断与自己持有的uuid相等，准备执行del。此时，锁的过期时间到了（或者因为其他原因，比如Redis的过期策略），锁自动失效（即被Redis删除）。线程B此时尝试获取锁"lock1"，由于锁已失效，线程B成功获取锁（setIfAbsent成功），设置值为"uuidB"。线程A继续执行del操作，删除了锁"lock1"（此时删除的是线程B刚设置的锁）。结果：线程B的锁被线程A删除，导致线程B在持有锁的过程中，锁被意外删除，其他线程（比如线程C）可能又会获得锁，导致多个线程同时进入临界区。Lua脚本保证原子性Lua脚本是redis已经内置的一种轻量小巧语言，其执行是通过redis的eval/evalsha命令来运行，把操作封装成一个Lua脚本，如论如何都是一次执行的原子操作if redis.call('get', KEYS[1]) == ARGV[1] then -- 执行删除操作 return redis.call('del', KEYS[1]) else -- 不成功，返回0 return 0 end// 解锁脚本 DefaultRedisScript<Object> unlockScript = new DefaultRedisScript(); unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lockDel.lua"))); // 执行lua脚本解锁 redisTemplate.execute(unlockScript, Collections.singletonList(keyName), value);而这种写法，同一个线程不允许获取同一把锁，在有些场景不适用，例如public void methodA() { lock.lock(); // 第一次加锁 try { // 业务逻辑1... methodB(); // 调用methodB } fi…

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决定端口然后打开左侧边栏的调试，选择相应的启动即可