首页
壁纸
统计
友链
Search
1
Vue2详细笔记
68 阅读
2
ExpressAPI
34 阅读
3
微信小程序
31 阅读
4
Spring MVC 请求生命周期
30 阅读
5
Nestjs概述-中文
29 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
40
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
40
篇与
的结果
2026-04-29
微信小程序
以下整理了一份生产环境微信小程序必备 API 及对应数据库表设计,覆盖登录鉴权、版本更新、性能监控、错误处理、容灾等核心模块。前端 API 均指微信原生能力,后端 API 为开发者服务器需要实现的接口。一、登录与用户身份1.1 前端 APIAPI说明wx.login()获取临时登录凭证 code,用于换取 openidwx.checkSession()检查当前登录态是否过期<button open-type="getPhoneNumber">获取用户手机号(需加密数据解密)wx.getUserProfile()获取用户头像昵称(必须用户手动点击允许)1.2 后端 API方法路径说明POST/api/user/login接收 code,调用微信 code2Session 换取 openid,生成自定义 token 返回POST/api/user/update-profile接收加密的手机号/用户信息,解密后更新用户资料GET/api/user/info通过 token 获取当前用户信息1.3 数据库表用户表 users字段类型说明idbigint(20)主键,自增openidvarchar(64)微信 openid,唯一索引unionidvarchar(64)微信 unionid(可选)nicknamevarchar(64)用户昵称avatar_urlvarchar(512)头像地址phonevarchar(20)手机号(加密存储)created_atdatetime创建时间updated_atdatetime更新时间登录凭证表 user_tokens字段类型说明idbigint(20)主键,自增user_idbigint(20)关联 users.idtokenvarchar(256)jwt tokenplatformtinyint(2)登录平台(微信小程序=1)expire_atdatetime过期时间created_atdatetime创建时间可选用 JWT 无状态方案,无需存储 token,但生产环境建议保留该表以支持强制下线、多设备管理。二、版本更新管理2.1 前端 APIAPI说明wx.getUpdateManager()管理小程序版本更新,提示用户重启2.2 后端 API(可选)方法路径说明GET/api/config/version返回最低要求版本号,前端可在启动时检查,若当前版本过低可引导升级或限制使用2.3 数据库表应用版本表 app_versions字段类型说明idint(11)主键,自增versionvarchar(20)版本号,如 "3.2.1"min_versionvarchar(20)最低兼容版本update_descvarchar(512)更新描述force_updatetinyint(1)是否强制更新 1=是 0=否platformvarchar(20)平台,如 "weapp"created_atdatetime创建时间即使有小程序自带的更新机制,此表仍可用于 首次冷启动时的版本拦截 或 后端对低版本用户做接口限制。三、网络状态监听3.1 前端 API(纯客户端,无后端接口)API说明wx.onNetworkStatusChange()监听网络切换(Wi-Fi / 4G / 无网络),触发界面提示或重连wx.onAppHide()应用切到后台,可手动关闭连接wx.onAppShow()应用切回前台,可重新校验登录态无需对应数据库表,但前端需在封装请求时根据网络状态做自动重试。四、性能监控与错误上报4.1 前端 APIAPI说明wx.getPerformance()获取性能对象wx.reportPerformance(id, cost)上报自定义性能指标(id 需先在后台配置)wx.onError()监听脚本错误wx.onUnhandledRejection()监听未处理的 Promise 拒绝wx.onPageNotFound()页面不存在监听wx.onLazyLoadError()异步组件加载失败监听4.2 后端 API方法路径说明POST/api/log/error接收客户端 JS 异常、网络请求错误等POST/api/log/performance接收页面加载耗时、接口耗时等性能数据4.3 数据库表错误日志表 error_logs字段类型说明idbigint(20)主键,自增user_idbigint(20)关联用户(可空)pagevarchar(256)错误发生的页面路径error_typevarchar(64)错误类型(JS错误、接口错误、资源错误)messagetext错误消息stacktext错误堆栈device_infojson设备信息(型号、系统、微信版本等)app_versionvarchar(20)小程序版本号occur_atdatetime发生时间created_atdatetime上报时间性能日志表 performance_logs字段类型说明idbigint(20)主键,自增user_idbigint(20)关联用户(可空)pagevarchar(256)页面路径metricvarchar(64)指标名(如 page_load_time)valueint(11)耗时(毫秒)extrajson扩展信息(网络类型等)app_versionvarchar(20)版本号created_atdatetime上报时间这两个表是生产环境问题排查的基础,必须建好并定期归档。五、通用配置与容灾5.1 后端 API方法路径说明GET/api/config/all拉取客户端需要的全局配置(如客服电话、公告、功能开关)5.2 数据库表配置表 configs字段类型说明idint(11)主键,自增keyvarchar(64)配置键,唯一valuetext配置值(支持 JSON)descriptionvarchar(256)描述updated_atdatetime更新时间典型配置项:maintenance_mode:是否开启维护模式force_update_version:强制更新最低版本contact_phone:客服电话home_banner:首页轮播图数据六、数据安全补充6.1 前端策略所有网络请求必须使用 https / wss敏感数据存储时使用 wx.setStorageSync 配合加密(或使用 wx.setStorage 的安全标记 encrypted)6.2 后端策略appsecret 严禁出现在前端代码,只能在服务端调用微信接口生成的 session_key 只能保存在服务端,不可返回给前端所有接口做好参数校验和限流6.3 对应数据库表(可选)密钥管理表 app_secrets(服务端内部)字段类型说明idint(11)主键app_idvarchar(32)小程序 appidsecretvarchar(128)appsecret(加密存储)token_expireint(11)自定义登录态有效时间(秒)created_atdatetime创建时间七、完整 API 清单总结优先级模块前端 API后端 API数据库表🔴 最高登录鉴权wx.login wx.checkSession getPhoneNumber/api/user/login /api/user/infousers user_tokens🔴 最高错误监控wx.onError onUnhandledRejection /onPageNotFound/api/log/errorerror_logs🟡 高版本更新wx.getUpdateManager/api/config/versionapp_versions🟡 高性能上报wx.reportPerformance getPerformance/api/log/performanceperformance_logs🟡 高网络监听onNetworkStatusChange onAppHide/Show——🟢 中全局配置—/api/config/allconfigs🟢 中数据安全——app_secrets以上内容足以支撑一个小程序的生产环境稳定运行。建议在 app.js 启动时统一完成如下初始化工作:拉取全局配置 + 版本检查校验登录态(有则续期,无则重新登录)注册全局错误监听注册网络变化监听启动更新管理器这样可确保小程序从启动到使用的全链路具备高可用性。
2026年04月29日
31 阅读
0 评论
0 点赞
2026-04-17
今早学习笔记
今早学习笔记(父子组件通信 + Vuex 核心)一、父子组件通信(企业级规范)核心原则:父业务 + 子UI,单向数据流核心逻辑:子组件只管表单渲染、交互,父组件只管业务、接口,不混乱、高复用1. 子组件(表单/弹窗)职责只负责:表单渲染、数据校验、弹窗显隐(自己管理状态)、收集表单数据不负责:不写任何API请求、不处理业务逻辑核心方法:- open\(row\):供父组件调用,打开弹窗(支持新增/编辑,row为编辑数据) - close\(\):自己关闭弹窗,重置表单 - submit\(\):表单校验通过后,通过 $emit 把数据抛给父组件 状态管理:弹窗isShow、表单formData,全部写在子组件data中,自己管理2. 父组件(列表页)职责负责:列表数据、接口请求(新增/编辑/删除)、页面刷新、成功/失败提示核心操作:- 通过 ref 调用子组件方法(open\(\)),打开弹窗 - 监听子组件 $emit 事件,接收表单数据,调用对应API - API请求成功后,刷新列表,给出提示 3. 通信方式(标准写法)父 → 子:ref 调用子组件方法(如:this.$refs.formRef.open())子 → 父:$emit 抛数据(如:this\.$emit(\&\#39;submit\&\#39;, formData))禁忌:子组件不能直接修改props,不能写API,父组件不插手子组件内部状态4. 示例核心代码(回顾)// 子组件:只抛数据,不调API handleSubmit() { this.$refs.form.validate(valid => { if (valid) { this.$emit('submit', this.formData); // 抛数据给父组件 this.close(); } }); } // 父组件:接收数据,调API async handleSubmit(formData) { try { formData.id ? await updateApi(formData) : await addApi(formData); this.$Message.success('操作成功'); this.getList(); // 刷新列表 } catch (e) { this.$Message.error('操作失败'); } }二、API交互规范(企业级)核心原则:API 按“全局/页面”划分存放,不滥用1. API 存放位置(关键区分)API类型存放位置典型例子原因全局公共APIVuex 的 action 中获取用户信息、权限、菜单、登录、登出全局共享,调用后需更新Vuex全局状态,多页面复用页面业务API父组件中列表查询、新增/编辑用户、修改密码、删除仅当前页面使用,数据只渲染当前页面,不共享组件私有API子组件中(极少)仅子组件自身使用的极小接口不影响其他组件,无需全局/父组件管理2. 禁忌禁止子组件写业务API(如新增、修改密码),影响复用性禁止把页面私有API放进Vuex,导致store臃肿、混乱禁止API调用后不处理异常(需加try/catch,给出提示)三、Vuex 核心内容(重点中的重点)核心定位:全局状态管理中心,解决跨组件共享数据、多层通信问题1. Vuex 5大核心关键词(定义+作用+示例)(1)State定义:Vuex 的数据源,存储全局共享数据(唯一数据源)通俗说:全局的 data,所有组件都能读取(不能直接修改)示例: `const state = \{userInfo: {}, // 全局用户信息permissions: [] // 全局权限码}`易错点:不能直接修改 state(如:this.$store.state.userInfo = {} 错误)(2)Mutation定义:唯一能修改 State 的地方,必须是同步函数(不能写异步)作用:同步修改全局状态,接收 state 和 自定义参数示例: `const mutations = \{// state:Vuex自动传入;data:commit时传入的参数SET\_USER\_INFO(state, data) { state.userInfo = data; // 唯一正确修改state的方式}}`规则:只能同步,不能写API、定时器(3)Action定义:处理异步操作(API请求)、复杂业务逻辑,不直接修改state作用:调API,拿到数据后,通过 commit 调用 Mutation 修改state核心语法:第一个参数是 context 上下文对象,可解构简化 `// 解构写法(ES6,推荐)async getUserInfo({ commit }) {const res = await getUserInfoApi(); // 异步调APIcommit(\&\#39;SET\_USER\_INFO\&\#39;, res); // 调用mutation改state}// 不解构写法(兼容ES5)async getUserInfo(context) { const res = await getUserInfoApi(); context.commit(\&\#39;SET\_USER\_INFO\&\#39;, res); // 完整写法}`补充:Action 也能处理同步逻辑,还能通过 dispatch 调用其他Action(4)Getter定义:Vuex 的全局计算属性,对 state 数据进行加工、过滤作用:派生数据,不修改原state,多组件复用相同计算逻辑示例: `// 全局getters\.jsconst getters = {userId: state =\> state.user.userInfo.user\_id, // 派生用户IDhasPerm: state =\> (permCode) =\> { // 派生权限判断方法 return state.user.permissions.includes(permCode);}}`(5)Module定义:Vuex 模块化拆分,按业务分模块管理(避免代码臃肿)核心:必须开启 namespaced: true(命名空间),防止模块间命名冲突示例(user模块): `export default \{namespaced: true, // 开启命名空间(企业级必开)state: {},mutations: {},actions: {}}`模块合并:在 store/index.js 中导入所有子模块,合并到 modules 中2. 关键调用关键词(commit / dispatch)关键词使用位置调用对象作用示例commitAction 内部(主要)、组件(极少)Mutation触发同步修改statecommit(\&\#39;user/SET\_USER\_INFO\&\#39;, res)dispatch组件(主要)、Action 内部Action触发异步操作/业务逻辑this.$store.dispatch(\&\#39;user/getUserInfo\&\#39;)3. 组件中使用 Vuex(map系列辅助函数)核心原则:数据类(state、getters)放 computed;方法类(mutations、actions)放 methods示例(完整写法): `import \{ mapState, mapGetters, mapMutations, mapActions \} from \&\#39;vuex\&\#39; export default { computed: {// 子模块state(带命名空间,必须写模块名) \.\.\.mapState\(\&\#39;user\&\#39;, \[\&\#39;userInfo\&\#39;, \&\#39;permissions\&\#39;\]\), // 全局getters(不用写模块名) \.\.\.mapGetters\(\[\&\#39;userId\&\#39;, \&\#39;hasPerm\&\#39;\]\)}, methods: {// 子模块mutations(带命名空间) \.\.\.mapMutations\(\&\#39;user\&\#39;, \[\&\#39;SET\_USER\_INFO\&\#39;\]\), // 子模块actions(带命名空间) \.\.\.mapActions\(\&\#39;user\&\#39;, \[\&\#39;getUserInfo\&\#39;, \&\#39;logout\&\#39;\]\), // 调用方式(映射后直接调用) login\(\) \{ this\.getUserInfo\(\); // 等价于 this\.$store\.dispatch\(\&\#39;user/getUserInfo\&\#39;\) this\.SET\_USER\_INFO\(data\); // 等价于 this\.$store\.commit\(\&\#39;user/SET\_USER\_INFO\&\#39;, data\) \}}}`易错点:- mapState/mapGetters 不能放 methods 里,反之亦然 - 带命名空间的模块,映射时必须写模块名(如 \&\#39;user\&\#39;) 4. 多参数传递(Vuex 官方规则)规则:Mutation / Action 只能接收 1 个自定义参数多参数解决方案:打包成一个对象传递 `// Action 中 commit(多参数打包)commit(\&\#39;SET\_USER\_INFO\&\#39;, {userInfo: res,test: \&\#39;测试数据\&\#39;})// Mutation 中接收(解构对象)SET\_USER\_INFO(state, { userInfo, test }) { state.userInfo = userInfo; state.testData = test;}`5. 兼容写法(不支持ES6/ES2020)现代语法(ES6+)兼容写法(ES5)解构 { commit }context.commit(不解构,完整调用)obj?.a?.b(可选链)obj \&\& obj.a \&\& obj.b(逻辑与替代)SET\_USER\_INFO(state, { a, b })SET\_USER\_INFO(state, payload) → const a = payload.a补充:实际开发中用 Babel 自动转译,不用手写兼容代码6. Vuex 模块化目录结构(企业级标准)src/store/ ├── index.js # 入口文件,合并所有子模块、挂载Vuex ├── getters.js # 全局getter汇总 └── modules/ # 业务模块拆分 ├── user.js # 用户模块(信息、权限) ├── permission.js # 权限模块(菜单、按钮权限) └── layout.js # 布局模块(侧边栏、全屏)四、核心易错点汇总(重点记忆)调用Action:this.$store\.dispatch\(\)(不是 $state,dispatch 不拼写错误)Mutation 只能同步,Action 可异步,不能搞反修改state必须通过Mutation,不能直接修改Vuex模块必须开启 namespaced: true,否则会命名冲突map系列辅助函数:数据类放computed,方法类放methods多参数必须打包成对象,Vuex不支持多个自定义参数API存放:全局公共API放Vuex,页面业务API放父组件五、整体核心流程(串联所有知识点)组件点击操作(如打开弹窗)→ 通过 ref 调用子组件 open() 方法子组件完成表单交互、校验 → 通过 $emit 把数据抛给父组件父组件接收数据 → 调用页面业务API(新增/编辑等)→ 提示+刷新列表若涉及全局数据(如用户信息)→ 组件 dispatch 调用 Vuex 的 ActionAction 调全局API → 拿到数据后 commit 调用 MutationMutation 同步修改 state → 组件通过 mapState/mapGetters 读取数据,视图更新通用企业级设计思路:父业务 + 子 UI 表单弹窗模式这套思路适用于所有后台管理系统:新增弹窗、编辑弹窗、表单抽屉、密码框、配置面板等,只要是「列表 + 表单弹窗」的结构,都能直接套用。全程遵守:单向数据流、职责分离、高复用、无全局污染,是国内互联网公司后台系统的标准范式。一、核心设计原则(4 条铁律)子组件只做 UI,不碰业务子组件 = 纯渲染组件:只负责表单、校验、弹窗开关、收集数据,绝不写接口。父组件只做业务,不碰渲染细节父组件 = 业务控制器:负责列表、接口请求、状态管理、刷新页面。通信只走两条路父 → 子:ref 调用方法子 → 父:$emit 抛数据谁的状态谁管理弹窗显隐、表单数据,全部由子组件自己管理,父组件不插手内部状态。二、通用职责划分(背会直接用)🔹 子组件(Form 弹窗)负责:弹窗显示 / 隐藏(isShow 写在自己 data)表单数据(formData)表单校验规则打开 / 重置 / 关闭方法表单校验通过后,把数据抛给父组件不写任何 API、不请求后端🔹 父组件(List 列表页)负责:表格数据、分页、查询点击按钮 → 通过 ref 打开子组件接收子组件抛来的表单数据调用新增 / 编辑 / 删除接口接口成功 / 失败提示刷新列表数据三、通用开发步骤(万能流程)拆分组件一个业务弹窗 = 一个独立子组件新增用户 → UserAddForm.vue编辑密码 → LoginPwdForm.vue配置参数 → ConfigForm.vue编写子组件(固定结构)写弹窗(Modal/Drawer)写表单 + 校验规则data 里定义:isShow、formData、loading写公共方法:open(row)、close()、submit()校验通过后 $emit('submit', formData)父组件引入并挂载import 子组件注册 components模板中放入子组件,绑定 ref 和 @submit点击按钮调用 this.$refs.xxx.open()父组件处理业务逻辑接收子组件数据调用对应 API成功提示 + 刷新列表失败提示
2026年04月17日
15 阅读
0 评论
0 点赞
2026-04-10
JUnit 5 单元测试完整笔记
JUnit 5 单元测试完整笔记一、JUnit 5 概述JUnit 5 是 Java 生态中最主流的单元测试框架,提供丰富的注解、断言和扩展机制。1.1 框架构成(三个子项目)子项目说明JUnit Platform在 JVM 上启动测试框架的基础平台,定义了 TestEngine APIJUnit Jupiter包含 JUnit 5 全新的编程模型和扩展机制JUnit Vintage提供对 JUnit 3 和 JUnit 4 测试的兼容运行支持二、核心测试注解JUnit 5 通过注解控制测试执行流程与行为。注解说明使用场景@Test标记一个方法为测试方法基本单元测试@BeforeEach在每个测试方法执行前运行测试数据准备、对象初始化@AfterEach在每个测试方法执行后运行资源清理、状态重置@BeforeAll在当前测试类的所有测试方法执行前运行一次(需为 static)类级别初始化(如建立数据库连接)@AfterAll在所有测试方法执行后运行一次(需为 static)类级别资源释放@DisplayName为测试类或方法自定义易读的显示名称提升测试报告的可读性@Disabled临时禁用某个测试方法或类跳过尚未实现或需要暂时忽略的测试@ParameterizedTest声明参数化测试方法使用多组数据驱动同一测试逻辑@RepeatedTest重复执行指定次数的测试验证方法稳定性或简单压力测试@Timeout指定测试方法执行超时时间性能验证、防止死循环三、断言方法(Assertions)JUnit 5 提供多种断言来验证测试结果。方法用途assertEquals(expected, actual)断言期望值与实际值相等assertTrue(condition) / assertFalse(condition)断言条件为真 / 假assertThrows(ExceptionType.class, executable)断言执行代码会抛出指定异常assertAll(executables...)组合多个断言,所有断言都会执行再报告失败(避免因一个失败而中断后续验证)四、参数化测试数据源通过 @ParameterizedTest 结合数据源注解,用多组数据测试同一逻辑。注解说明@ValueSource提供简单值数组(如 ints = {1, 2, 3})@CsvSource提供 CSV 格式的字符串数据,支持多列参数@MethodSource引用一个返回 Stream / Iterable 的静态方法提供测试数据@EnumSource使用枚举类的所有值或指定子集作为测试数据五、测试编写原则与最佳实践5.1 好的测试实践(推荐)✅ 示例代码@Test @DisplayName("当输入两个正数时应该返回正确的和") void shouldReturnSumWhenAddingTwoPositiveNumbers() { // Arrange - 准备测试数据 Calculator calculator = new Calculator(); // Act - 执行被测试的方法 int result = calculator.add(2, 3); // Assert - 验证结果 assertEquals(5, result); }✅ 核心原则AAA 模式:Arrange(准备) → Act(执行) → Assert(断言)命名清晰:测试方法名应具有描述性,表达“测什么”和“预期结果”使用 @DisplayName:进一步提高测试报告可读性单一职责:每个测试方法只验证一个功能点覆盖边界条件与异常:不仅要测试正常路径,还要测试空值、零值、负数、越界等异常场景5.2 应该避免的实践(反模式)❌ 反面示例@Test void test1() { // 测试多个不相关的功能 assertEquals(5, calculator.add(2, 3)); assertEquals(6, calculator.multiply(2, 3)); assertEquals("hello", stringUtils.reverse("olleh")); }❌ 错误做法测试方法名无意义(如 test1、testAdd)一个测试方法中验证多个不相关的功能点测试之间存在执行顺序或数据依赖忽略边界条件与异常分支测试代码逻辑过于复杂或包含大量重复代码六、常用测试类型类型说明基本功能测试验证方法在正常输入下的预期行为边界值测试测试输入为 null、空字符串、0、最大值、最小值等边界情况异常测试使用 assertThrows 验证错误输入时是否正确抛出异常参数化测试使用多组数据驱动同一测试逻辑,减少重复代码性能测试结合 @Timeout 或性能测试框架验证执行效率七、测试编写注意事项测试独立性:每个测试应能单独运行,不依赖其他测试的执行结果或执行顺序。数据自包含:测试数据应在测试方法内部准备,避免使用共享可变全局状态。断言消息:为关键断言添加描述性消息,便于快速定位失败原因。资源清理:在 @AfterEach 或 @AfterAll 中释放资源(如文件流、数据库连接)。代码简洁:保持测试代码易读、易维护,必要时可提取私有辅助方法。八、测试覆盖率目标指标建议值整体代码覆盖率≥ 80%核心业务逻辑重点覆盖,尽量达到 90% 以上公共方法所有 public 方法均应有对应测试异常分支与边界条件必须包含相关测试用例覆盖率工具使用 JaCoCo 等工具生成报告并持续监控笔记总结完毕。 按照以上结构和内容复习,即可系统掌握 JUnit 5 的使用方法与单元测试最佳实践。
2026年04月10日
10 阅读
0 评论
0 点赞
2026-04-10
Redis 核心数据类型与命令参考手册
Redis 核心数据类型与命令参考手册一、 Redis 数据类型概览数据类型核心特性适用场景字符串 (String)二进制安全,最大512MB,支持原子计数操作。缓存用户信息、计数器(访问量、点赞)、分布式锁、会话存储。哈希 (Hash)键值对集合,适合存储对象,节省内存。每个哈希可存 2^32 - 1 个字段。存储用户/商品信息、配置管理、购物车数据。列表 (List)有序字符串列表,支持双端操作,最多可存 2^32 - 1 个元素。消息队列、最新动态列表、任务队列、历史记录。集合 (Set)无序且唯一的字符串集合,支持集合运算(交、并、差)。标签系统、好友关系、权限管理、去重处理。有序集合 (Sorted Set)元素唯一,按分数(Score)排序,支持范围查询。排行榜、优先级队列、时间线、价格区间查询。二、 通用命令与规范1. 命令语法规范大小写:命令名称不区分大小写(建议大写)。分隔符:参数之间用空格分隔。参数表示:[ ] 表示可选参数,| 表示多选一。2. 键(Key)的命名规范风格:使用冒号 : 作为分隔符,采用层次化结构。示例:user:1001:profile、cache:article:1001、lock:order:1001。原则:使用有意义的名称,保持一致的命名风格,避免过长的键名。3. 键的通用操作命令命令描述示例EXISTS key检查键是否存在EXISTS user:1001TYPE key返回键的数据类型TYPE user:1001DEL key [key ...]删除一个或多个键DEL user:1001 user:1002RENAME key newkey重命名键RENAME oldname newnameEXPIRE key seconds设置键的过期时间(秒)EXPIRE session:abc123 3600TTL key查看键的剩余生存时间(秒)TTL session:abc123PERSIST key移除键的过期时间PERSIST session:abc123KEYS pattern查找匹配模式的键 (生产环境慎用,会阻塞)KEYS user:*SCAN cursor [MATCH pattern]非阻塞式迭代数据库中的键SCAN 0 MATCH user:* COUNT 10DBSIZE返回当前数据库的键数量DBSIZE三、 数据类型详解与命令参考1. 字符串 (String)基本操作命令描述示例SET key value设置字符串值SET name "Redis"GET key获取字符串值GET nameAPPEND key value追加字符串到末尾APPEND name " Database"STRLEN key获取字符串长度STRLEN name数值操作(原子性)命令描述示例INCR key将数值递增 1INCR page:viewsINCRBY key increment将数值增加指定值INCRBY user:1001:points 100DECR key将数值递减 1DECR counterDECRBY key decrement将数值减少指定值DECRBY user:1001:points 50批量与条件操作命令描述示例MSET k1 v1 k2 v2批量设置多个键值对MSET k1 v1 k2 v2MGET k1 k2批量获取多个键的值MGET k1 k2SETNX key value仅当键不存在时设置 (常用于分布式锁)SETNX lock:order "locked"SETEX key seconds value设置值并指定过期时间SETEX session:abc 3600 "data"SET key value EX seconds NX组合命令:带过期时间的锁SET lock:key "val" EX 30 NX2. 哈希 (Hash)基本操作命令描述示例HSET key field value设置哈希字段的值HSET user:1001 name "张三"HGET key field获取哈希字段的值HGET user:1001 nameHDEL key field删除哈希字段HDEL user:1001 temp_fieldHLEN key获取哈希字段数量HLEN user:1001HKEYS key获取所有字段名HKEYS user:1001HVALS key获取所有字段值HVALS user:1001HGETALL key获取所有字段和值 (慎用,大哈希会阻塞)HGETALL user:1001批量与数值操作命令描述示例HMSET key f1 v1 f2 v2批量设置多个字段HMSET user:1002 name "李四" age 30HMGET key f1 f2批量获取多个字段的值HMGET user:1002 name ageHINCRBY key field increment将字段值增加指定整数HINCRBY user:1001 login_count 1HSCAN key cursor MATCH pattern迭代哈希中的字段HSCAN user:1001 0 MATCH "*count*"3. 列表 (List)命令描述示例LPUSH key value [value ...]从左侧(头部)插入元素LPUSH mylist "world" "hello"RPUSH key value [value ...]从右侧(尾部)插入元素RPUSH mylist "redis"LPOP key从左侧弹出元素LPOP mylistRPOP key从右侧弹出元素RPOP mylistLLEN key获取列表长度LLEN mylistLRANGE key start stop获取指定范围元素LRANGE mylist 0 2LTRIM key start stop保留指定范围的元素(修剪列表)LTRIM mylist 0 99BLPOP key timeout阻塞式左端弹出(队列用)BLPOP queue1 30BRPOP key timeout阻塞式右端弹出BRPOP queue1 304. 集合 (Set)命令描述示例SADD key member [member ...]添加元素到集合SADD tags "tech" "redis"SREM key member从集合中删除元素SREM tags "tech"SISMEMBER key member检查元素是否存在SISMEMBER tags "redis"SMEMBERS key获取集合所有元素 (慎用,大集合会阻塞)SMEMBERS tagsSCARD key获取集合元素数量SCARD tagsSINTER key1 key2计算集合交集SINTER set1 set2SUNION key1 key2计算集合并集SUNION set1 set2SDIFF key1 key2计算集合差集 (key1 - key2)SDIFF set1 set2SSCAN key cursor MATCH pattern迭代集合中的元素SSCAN tags 0 MATCH "user:*"5. 有序集合 (Sorted Set)命令描述示例ZADD key score member [score member ...]添加元素及分数ZADD leaderboard 100 "player1"ZREM key member删除元素ZREM leaderboard "player1"ZSCORE key member获取元素的分数ZSCORE leaderboard "player1"ZINCRBY key increment member增加元素的分数ZINCRBY leaderboard 10 "player1"ZCARD key获取集合元素总数ZCARD leaderboard范围查询 ZRANGE key start stop [WITHSCORES]按排名升序获取 (0为第1名)ZRANGE leaderboard 0 2 WITHSCORESZREVRANGE key start stop [WITHSCORES]按排名降序获取ZREVRANGE leaderboard 0 2ZRANGEBYSCORE key min max按分数范围升序获取ZRANGEBYSCORE leaderboard 80 100ZRANK key member获取元素升序排名 (从0开始)ZRANK leaderboard "player1"ZREVRANK key member获取元素降序排名ZREVRANK leaderboard "player1"删除与运算 ZREMRANGEBYRANK key start stop按排名范围删除元素ZREMRANGEBYRANK leaderboard 0 2ZREMRANGEBYSCORE key min max按分数范围删除元素ZREMRANGEBYSCORE leaderboard 0 60ZUNIONSTORE dest numkeys key [key ...]计算并集并存储ZUNIONSTORE result 2 set1 set2ZINTERSTORE dest numkeys key [key ...]计算交集并存储ZINTERSTORE result 2 set1 set2四、 补充:HyperLogLog (基数估算)一种概率性数据结构,用于在极小的内存(12KB)下估算海量数据的去重元素数量(基数),误差率约 0.81%。命令描述PFADD key element [element ...]添加元素PFCOUNT key返回估算的基数PFMERGE destkey sourcekey [sourcekey ...]合并多个 HyperLogLog五、 性能与选择建议键管理:生产环境严禁使用 KEYS,改用 SCAN;大集合遍历用 SSCAN/HSCAN/ZSCAN。批量操作:优先使用 MSET/MGET、HMSET/HMGET 减少网络开销。过期时间:缓存数据务必设置 EXPIRE,防止内存无限增长。数据类型选型:需要去重且无序:Set。需要排序且唯一:Sorted Set。需要有序但允许重复:List。存储对象且需单独更新字段:Hash。简单缓存/计数器:String。
2026年04月10日
10 阅读
0 评论
0 点赞
2026-04-09
并发编程基础概念
一、并发编程基础概念1.1 并发与并行并发:多任务交替执行(单核/多核),依赖 CPU 时间片轮转。宏观上是同时进行的,微观上是串行。并行:多任务同时执行(必须多核),利用多个 CPU 核心同时处理不同任务。1.2 Java 内存模型 (JMM)主内存:所有线程共享,存储实例变量、静态变量。工作内存:每个线程私有,存储主内存变量的副本。线程不能直接操作主内存,必须先读入工作内存,操作完成后再写回。JMM 定义的 8 种内存交互操作(原子指令):lock(锁定):作用于主内存,将变量标识为线程独占。unlock(解锁):作用于主内存,释放独占状态。read(读取):作用于主内存,将变量值传输到工作内存。load(载入):作用于工作内存,将 read 的值放入变量副本。use(使用):作用于工作内存,将变量值传递给执行引擎。assign(赋值):作用于工作内存,将执行引擎的值赋给变量副本。store(存储):作用于工作内存,将变量值传送到主内存。write(写入):作用于主内存,将 store 的值写入主内存变量。1.3 线程安全问题的根源与表现根源:主内存与工作内存数据不一致。竞态条件:结果依赖线程执行的相对时序(如 check-then-act)。数据竞争:多线程访问同一内存,至少一个写操作,且无同步。死锁:两个及以上线程互相持有对方需要的锁,永久阻塞。二、并发编程三大特性(详细版)特性详细描述实现手段可见性线程 A 修改变量后,线程 B 能立即看到变化。volatile、synchronized、Lock(锁释放前刷新主存)、final原子性一个或多个操作要么全执行,要么全不执行,中间不被调度中断。synchronized、Lock、java.util.concurrent.atomic 原子类有序性避免指令重排序带来的逻辑错误。单线程内语义表现为串行。volatile(内存屏障)、synchronized(锁释放与获取原则)三、线程基础 API 详解3.1 创建线程的方式(含代码细节)方式一:继承 Thread 类public class ThreadExample extends Thread { @Override public void run() { System.out.println("Thread is running"); } public static void main(String[] args) { ThreadExample t = new ThreadExample(); t.start(); } }方式二:实现 Runnable 接口public class RunnableExample implements Runnable { @Override public void run() { System.out.println("Runnable task is running"); } public static void main(String[] args) { Thread t = new Thread(new RunnableExample()); t.start(); // Lambda 简化写法 new Thread(() -> System.out.println("Lambda style")).start(); } }方式三:实现 Callable 接口(有返回值、可抛异常)import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableExample implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Callable result"; } public static void main(String[] args) throws Exception { FutureTask<String> futureTask = new FutureTask<>(new CallableExample()); new Thread(futureTask).start(); // 阻塞获取结果 System.out.println(futureTask.get()); } }3.2 Thread 类构造方法与核心控制构造方法细节:Thread():默认线程名 "Thread-" + n。Thread(String name):指定线程名。Thread(Runnable target):指定任务。Thread(Runnable target, String name):指定任务与名称。线程优先级与守护线程:Thread t = new Thread(); t.setPriority(Thread.MAX_PRIORITY); // 10(最高) t.setPriority(Thread.NORM_PRIORITY); // 5(默认) t.setPriority(Thread.MIN_PRIORITY); // 1(最低) // 守护线程:JVM 退出时不管守护线程是否结束 Thread daemonT = new Thread(() -> { while (true) { /* 后台任务,如日志清理 */ } }); daemonT.setDaemon(true); // 必须在 start() 前设置 daemonT.start();3.3 线程中断机制(方法签名详解)void interrupt():设置中断标志位为 true。boolean isInterrupted():检查标志位,不清除。static boolean interrupted():检查当前线程标志位,并清除(重置为 false)。Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // 业务逻辑 try { Thread.sleep(1000); } catch (InterruptedException e) { // 捕获异常后标志位会被清除,需手动再次中断以退出循环 Thread.currentThread().interrupt(); } } });四、synchronized 锁机制深度解析4.1 锁的三种形态(代码示例)对象锁(锁当前实例 this):class SyncDemo { // 同步实例方法 public synchronized void method1() { /* 锁 this */ } // 同步代码块 public void method2() { synchronized (this) { /* 锁 this */ } } }类锁(锁 Class 对象):class SyncDemo { // 同步静态方法 public static synchronized void staticMethod1() { /* 锁 SyncDemo.class */ } public void staticMethod2() { synchronized (SyncDemo.class) { /* 锁类对象 */ } } }4.2 锁升级机制(Java 6+,不可逆)偏向锁:无竞争时,在对象头记录线程 ID,线程再次进入仅需检查 ID,无需 CAS。轻量级锁:交替执行时,通过 CAS 自旋尝试获取锁,不阻塞线程(消耗 CPU)。重量级锁:激烈竞争时,自旋失败升级为 OS 互斥量,线程挂起进入阻塞队列(开销大)。五、volatile 与内存屏障5.1 volatile 特性总结保证可见性:写 volatile 变量时强制刷新工作内存到主内存;读 volatile 变量时强制从主内存加载。禁止重排序:通过插入内存屏障防止 CPU/编译器对指令进行重排。不保证原子性:i++ 操作(读-改-写)依然不安全。5.2 内存屏障分类(JMM 规范)屏障类型作用示例场景LoadLoad屏障前读操作完成,才能执行屏障后读操作。读变量A; LoadLoad; 读变量B;LoadStore屏障前读操作完成,才能执行屏障后写操作。读变量A; LoadStore; 写变量B;StoreStore屏障前写操作完成并可见,才能执行屏障后写操作。写变量A; StoreStore; 写变量B;StoreLoad屏障前写操作完成并可见,才能执行屏障后读操作。开销最大,兼具其他三者功能。写变量A; StoreLoad; 读变量B;volatile 屏障插入策略:volatile 写:前插 StoreStore,后插 StoreLoad。volatile 读:后插 LoadLoad + LoadStore。六、Lock 接口与 ReentrantLock 详细 API6.1 Lock 接口方法清单方法详细描述void lock()阻塞获取锁,不可中断,死等。void lockInterruptibly()可响应中断的获取锁,等待时若线程被中断则抛 InterruptedException。boolean tryLock()非阻塞,获取到返回 true,否则立即返回 false。boolean tryLock(long time, TimeUnit unit)超时等待,超时返回 false;等待时可响应中断。void unlock()释放锁。必须在 finally 块中调用。Condition newCondition()返回绑定此锁的条件变量。6.2 ReentrantLock 使用模板class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock(); // 关键:防止死锁 } } }6.3 公平锁与非公平锁构造// 非公平锁(默认,吞吐量高) ReentrantLock unfairLock = new ReentrantLock(); ReentrantLock unfairLock2 = new ReentrantLock(false); // 公平锁(FIFO,避免线程饥饿但上下文切换多) ReentrantLock fairLock = new ReentrantLock(true);6.4 可中断与超时示例// 可中断锁示例 try { lock.lockInterruptibly(); // 临界区 } catch (InterruptedException e) { // 线程被中断,退出等待队列 } finally { lock.unlock(); } // 超时锁示例 if (lock.tryLock(2, TimeUnit.SECONDS)) { try { // 获取成功 } finally { lock.unlock(); } } else { // 执行备选方案 }七、读写锁与 StampedLock7.1 ReentrantReadWriteLock 特性读写互斥:读锁与写锁互斥。锁降级:持有写锁的线程可以获取读锁,然后释放写锁,降级为读锁。不支持锁升级:持有读锁时直接获取写锁会导致死锁,JVM 禁止此行为。ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 缓存更新场景:锁降级示例 writeLock.lock(); try { // 1. 更新数据库/缓存 readLock.lock(); // 在释放写锁前获取读锁 } finally { writeLock.unlock(); // 降级为读锁 } try { // 2. 后续读取操作(保证数据可见性) } finally { readLock.unlock(); }7.2 StampedLock 三种模式(Java 8+,不可重入)写锁:long stamp = stampedLock.writeLock(); 返回邮戳。悲观读锁:long stamp = stampedLock.readLock();乐观读:无锁机制,依赖版本校验。class Point { private double x, y; private final StampedLock sl = new StampedLock(); // 乐观读(读多写少性能最优) double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); // 获取乐观读邮戳 double currentX = x, currentY = y; // 无锁读取 if (!sl.validate(stamp)) { // 校验邮戳是否失效 stamp = sl.readLock(); // 失效则升级为悲观读 try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }八、Condition 条件变量(替换 Object Monitor)8.1 核心方法对比Condition 方法Object 对应方法说明await()wait()释放锁并等待。必须在 while 循环中调用以防虚假唤醒。signal()notify()唤醒一个等待线程。signalAll()notifyAll()唤醒所有等待线程。awaitUninterruptibly()无不可中断等待。awaitUntil(Date deadline)wait(long timeout)带截止时间等待。8.2 生产者-消费者模型(多条件队列)class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); // 防御性编程:必须用 while 循环 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) // 防止虚假唤醒 notFull.await(); // ... 入队逻辑 notEmpty.signal(); } finally { lock.unlock(); } } }九、原子类与 CAS 底层原理9.1 AtomicInteger 常用方法方法签名操作描述int get()获取当前值。void set(int newValue)设置新值(无原子性保证,仅可见性)。int getAndSet(int newValue)设置新值,返回旧值。boolean compareAndSet(expect, update)CAS 核心操作。int getAndIncrement()等价于 i++。int incrementAndGet()等价于 ++i。int getAndAdd(int delta)加 delta 并返回旧值。9.2 ABA 问题与解决方案ABA 问题描述:线程 1 读取值 A,线程 2 改为 B,线程 3 又改回 A。线程 1 使用 CAS 检查时发现值仍为 A,误以为未变化而更新成功。解决方案:AtomicStampedReference 引入版本号。// 泛型类:AtomicStampedReference<V> AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); // 初始值100,版本号0 int stamp = ref.getStamp(); int oldValue = ref.getReference(); // 更新时需同时匹配值和时间戳 boolean success = ref.compareAndSet(oldValue, 200, stamp, stamp + 1);
2026年04月09日
15 阅读
0 评论
0 点赞
1
...
3
4
5
...
8