月下·闲庭 - Springboot
type: channel
link: https://blog.domye.top/tag/Springboot/
items: vscode多端口启动Springboot项目
items: StringRedisTemplate使用笔记
items: JWT令牌详细解析
items: 苍穹外卖学习笔记
items: 黑马点评项目笔记

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

StringRedisTemplate使用笔记
type: item
date: 2025-07-21T18:11:00+08:00
description: 介绍StringRedisTemplate 是 Spring Data Redis 提供的专门操作 Redis 字符串数据的模板类，其键（key）和值（value）默认采用字符串序列化（StringRedisSerializer），无需额外配置即可高效处理文本数据。适用于缓存、会话管理等场景StringRedisTemplate 和 RedisTemplate 是 Spring Data Redis 中两个核心操作类，二者为继承关系（StringRedisTemplate 继承 RedisTemplate），但在序列化机制、数据存储格式及适用场景上有显著区别。以下从多个维度详细说明其关系与差异：特性StringRedisTemplateRedisTemplate继承关系子类（继承 RedisTemplate）父类（泛型类，支持任意类型）键序列化器StringRedisSerializer默认 JdkSerializationRedisSerializer值序列化器StringRedisSerializer默认 JdkSerializationRedisSerializer数据存储格式字符串（可读性强，如 "user:1000"）JDK 二进制序列化（不可读，如 \xac\xed\x00\x05t...）跨语言兼容性高（通用字符串格式）低（依赖 Java 反序列化）可视化友好性✅ Redis CLI 可直接查看数据❌ 二进制数据不可读核心特性与优势序列化简化默认使用 StringRedisSerializer，键值均为字符串，避免 JDK 序列化（如 RedisTemplate 的字节数组格式），数据在 Redis 中可读性更强。节省内存：不存储类信息（Class Metadata），降低内存占用。开箱即用无需自定义序列化，注入即可操作：@Autowired private StringRedisTemplate stringRedisTemplate;多数据类型支持通过不同接口操作 Redis 的五种核心数据结构：sdaopsForValue(); // 操作字符串 opsForHash(); // 操作哈希表 opsForList(); // 操作列表 opsForSet(); // 操作集合 opsForZSet(); // 操作有序集合使用场景对比数据类型优势适用场景典型 API字符串（String）简单高效，支持原子增减缓存、计数器、令牌管理set(), get(), increment()哈希（Hash）结构化存储对象属性用户信息、商品详情put(), entries(), get()列表（List）有序元素，支持双向操作消息队列、操作日志leftPush(), range(), leftPop()集合（Set）元素唯一性，快速查找标签系统、好友关系add(), members(), isMember()常用 API 分类详解字符串（String）操作方法作用示例set(key, value)存储键值对stringRedisTemplate.opsForValue().set("user:1", "Alice")set(key, value, timeout, timeUnit)存值并设置过期时间opsForValue().set("token", "xyz", 30, TimeUnit.MINUTES)get(key)根据键获取值String name = opsForValue().get("user:1")increment(key, delta)值递增（整数）opsForValue().increment("counter", 1)decrement(key, delta)值递减（整数）opsForValue().decrement("stock", 10)setIfAbsent(key, value)仅当键不存在时存储Boolean success = opsForValue().setIfAbsent("lock", "1")哈希（Hash）操作方法作用示例put(key, hashKey, value)向哈希表添加字段opsForHash().put("user:100", "age", "25")get(key, hashKey)获取哈希表字段值String age = opsForHash().get("user:100", "age")entries(key)获取整个哈希表Map user = opsForHash().entries("user:100")delete(key, hashKeys)删除哈希表字段opsForHash().delete("user:100", "address")列…

JWT令牌详细解析
type: item
date: 2025-07-19T18:21:00+08:00
description: 在当今的Web开发中，安全认证和授权变得尤为重要。 JWT（JSON Web Tokens）是一种开放标准（RFC 7519），它定义了一种简洁、紧凑且自包含的方式，用于在各方之间安全地传输信息。但这种传输并不是保密的传输,只是验证真伪的传输,信息是可以被看见的,只是做不了假jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。紧凑：指的是它能够以较小的体积包含所有的信息。JWT使用Base64Url编码算法JWT支持多种加密算法，如HS256、RS256等，这些算法能够在保证安全性的同时，保持令牌的大小不会过大。自包含：指的是jwt令牌，看似是一个随机的字符串，但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。JWT包含了所有必要的信息来验证其有效性，不需要额外的查询或数据库操作。简单来说就是将原始的json数据格式进行了安全的封装，这样就可以直接基于jwt在通信双方安全的进行信息传输JWT的组成JWT由三部分组成：头部（Header）、载荷（Payload）和签名（Signature）用.分隔。这三部分以.分隔，形如xxxxx.yyyyy.zzzzz头部包含了令牌的类型和所使用的加密算法编码后：eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9解码后：{ "alg": "HS256",//加密的算法 "typ": "JWT"//令牌的类型 }头部告诉我们签名算法（HS256）和令牌类型（JWT）载荷存放实际的数据，可以是任何形式的JSON对象编码后：eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ 解码后：{ "name": "John Doe", "admin": true, "roles": ["user", "admin"], "exp": 1687016741 }载荷包含了声明信息，如用户姓名、是否为管理员、用户角色和过期时间。签名用于验证令牌的发送者和内容是否被篡改。签名由头部、载荷、秘钥和指定的算法生成签名是对头部和载荷的加密，确保令牌的完整性和可靠性在生成JWT令牌时，会对JSON格式的数据进行一次编码：进行base64编码JWT使用导入JWT依赖<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>生成tokenMap<String, Object> claims = new HashMap<>(); claims.put(JwtClaimsConstant.EMP_ID, employee.getId()); String token = JwtUtil.createJWT( jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims);配置拦截器在WebMvcConfiguration中重写拦截器接口addInterceptors，每次访问/admin目录下的文件都会经过此拦截器进行jwt验证。@Configuration @Slf4j public class WebMvcConfiguration extends WebMvcConfigurationSupport { ​ @Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; ​ /** * 注册自定义拦截器 * * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/employee/login"); }验证解析token创建拦截器的类JwtInterceptor，用于配置WebMvcConfigurer，将自己的自定义拦截器装配到容器中；jwt拦截器文件JwtInterceptor，重写preHandle方法，校验通过拦截器放行，不通过返回错误的状态码。@Component @Slf4j public class JwtTokenAdminInterceptor implements…

苍穹外卖学习笔记
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对象…