【Redis】2、Redis应用之【根据 Session 和 Redis 进行登录校验和发送短信验证码】
目录
- 一、基于 Session 实现登录
- (1) 发送短信验证码
- ① 手机号格式后端校验
- ② 生成短信验证码
- (2) 短信验证码登录、注册
- (3) 登录验证
- ① 通过 SpringMVC 定义拦截器
- ② ThreadLocal
- (4) 集群 Session 不共享问题
- 二、基于 Redis 实现共享 session 登录
- (1) 登录之后,缓存 token 到客户端
- (2) 每次请求都携带 token
- (3) 短信验证码
- (4) 短信验证码登录、注册
- (5) 免登录
- (6) 刷新登录有效期
🌼 文章基于 B 站黑马程序员视频教程编写
🌼 做笔记便于日后复习
一、基于 Session 实现登录
(1) 发送短信验证码
① 手机号格式后端校验
手机号校验的正则表达式
/*** 正则表达式*/
public abstract class RegexPatterns {/*** 手机号正则*/public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";/*** 邮箱正则*/public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";/*** 密码正则:4~32 位的字母、数字、下划线*/public static final String PASSWORD_REGEX = "^\\w{4,32}$";/*** 验证码正则, 6位数字或字母*/public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";}
校验工具类:
public class RegexUtils {/*** 是否是无效手机格式** @param phone 要校验的手机号* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone) {return mismatch(phone, RegexPatterns.PHONE_REGEX);}/*** 是否是无效邮箱格式** @param email 要校验的邮箱* @return true:符合,false:不符合*/public static boolean isEmailInvalid(String email) {return mismatch(email, RegexPatterns.EMAIL_REGEX);}/*** 是否是无效验证码格式** @param code 要校验的验证码* @return true:符合,false:不符合*/public static boolean isCodeInvalid(String code) {return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);}// 校验是否不符合正则格式private static boolean mismatch(String str, String regex) {if (StrUtil.isBlank(str)) {return true;}return !str.matches(regex);}
}
② 生成短信验证码
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency>
🌼 hutool 工具的详细使用: https://doc.hutool.cn/pages/index/
/*** 发送短信验证码** @param phone 手机号* @param session 用户缓存验证码*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 校验手机号格式是否符合手机号的规范if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");}// 生成 6 位数字短信验证码String code = RandomUtil.randomNumbers(6);// 把短信验证码保存到服务端 session 中session.setAttribute("code", code);// 发送短信验证码给手机号 phonelog.info("向 {} 手机号发送了验证码:{}", phone, code);return Result.ok("发送验证码成功");}
(2) 短信验证码登录、注册
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {Result result = requestParamsValidate(loginForm, session);if (!result.getSuccess()) { // 手机号或验证码校验失败return result;}// 根据手机号查询用户String phone = loginForm.getPhone();User user = query().eq("phone", phone).one();// 根据手机号在数据中没有查询到用户信息if (user == null) {// 注册用户Result saveResult = saveUserByPhone(phone);if (saveResult.getSuccess()) { // 注册用户成功user = (User) saveResult.getData();} else {return saveResult;}}// 保存用户信息到 sessionsession.setAttribute("user", user);return Result.ok("登录成功");}private Result saveUserByPhone(String phone) {User newUser = new User();newUser.setNickName("USER_" + RandomUtil.randomString(9));newUser.setPhone(phone);if (save(newUser)) {return Result.ok(newUser);}return Result.fail("服务器忙, 用户保存到数据库失败");}private Result requestParamsValidate(LoginFormDTO loginForm, HttpSession session) {// 校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");}// 校验验证码是否存在Object cacheCode = session.getAttribute("code");String paramCode = loginForm.getCode();if (RegexUtils.isCodeInvalid(paramCode) || !paramCode.equals(cacheCode)) {return Result.fail("验证码错误");}return Result.ok();}
(3) 登录验证
🌿 根据 Cookie 中的 JSESSIONID 获取到 Session
🌿 然后从 Session 中获取到信息
🌿 登录校验需要在拦截器(Interceptor)中完成
🌿 SpringMVC 的 Interceptor 可以拦截 Controller,在请求到达 Controller 之前做一些事情
① 通过 SpringMVC 定义拦截器
- 实现(implements)HandlerInterceptor 接口
- 可覆盖该接口中的三个默认方法
🌼 preHandle
:在 Controller 的处理方法之前调用(当该方法的返回值为 true 的时候 才执行 Controller 里面的内容)
🌱通常在 preHandle
中进行初始化、请求预处理等操作(可进行登录验证)
🌱preHandle
返回 true 才会执行后面的调用。若返回 false,不会调用 Controller 处理方法、postHandle 和 afterCompletion
🌱当有多个拦截器时,preHandle 按照正序执行
🌼 postHandle
:在 Controller 的处理方法之后,DispatcherServlet
进行视图渲染之前调用
🌱可在 postHandle 中进行请求后续加工处理操作
🌱当有多个拦截器时,postHandle 按照逆序执行
🌼 afterCompletion
:在 DispatcherServlet 进行视图渲染之后调用
🌱一般在这里进行资源回收操作
🌱当有多个拦截器时,afterCompletion 按照逆序执行
配置拦截器:
@Configuration
public class MvcConfig implements WebMvcConfigurer {/*** 配置拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns( // 这些请求不可拦截"/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
② ThreadLocal
🌼 ThreadLocal 可以解释成线程的局部变量
🌼一个 ThreadLocal 的变量只有当前自身线程可以访问,别的线程都访问不了,那么自然就避免了线程竞争
🌼ThreadLocal 提供了一种与众不同的线程安全方式,它不是在发生线程冲突时想办法解决冲突,而是彻底避免了冲突的发生
/*** 用于保存 UserDTO 的 ThreadLocal 的封装*/
public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user) {tl.set(user);}public static UserDTO getUser() {return tl.get();}public static void removeUser() {tl.remove();}
}
登录拦截器:
/*** 登录校验拦截器*/
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {HttpSession session = request.getSession();Object user = session.getAttribute("user");if (null == user) {response.setStatus(401);return false; // 拦截}UserDTO userDTO = user2UserDto((User) user);// 保存用户信息到 ThreadLocal 中UserHolder.saveUser(userDTO);return true;}private UserDTO user2UserDto(User user) {UserDTO userDTO = new UserDTO();userDTO.setIcon(user.getIcon());userDTO.setId(user.getId());userDTO.setNickName(user.getNickName());return userDTO;}/*** 资源释放*/@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception {UserHolder.removeUser();}@Overridepublic void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {}
}
@GetMapping("/me")public Result me() {// 获取当前登录的用户并返回UserDTO userDto = UserHolder.getUser();return Result.ok(userDto);}
🌼
/me
这个 Controller 执行完毕后,LoginInterceptor 的afterCompletion
会被调用
(4) 集群 Session 不共享问题
二、基于 Redis 实现共享 session 登录
🎉 登录之后,每次发起请求都要携带 token(用户登录凭证)
(1) 登录之后,缓存 token 到客户端
login() {const {radio, phone, code} = this.formif (!radio) {this.$message.error("请先确认阅读用户协议!");return}if (!phone || !code) {this.$message.error("手机号和验证码不能为空!");return}if (phone.length !== 11) {this.$message.error("手机号格式错误!");return}axios.post("/user/login", this.form).then(({data}) => {if (data) {// 保存用户信息到 sessionsessionStorage.setItem("token", data);}// 跳转到首页location.href = "/info.html"}).catch(err => {console.log(err)this.$message.error(err)})},
(2) 每次请求都携带 token
let commonURL = "/api";
// 设置后台服务地址
axios.defaults.baseURL = commonURL
axios.defaults.timeout = 2000// request 拦截器,将用户 token 放入头中
let token = sessionStorage.getItem("token")// 请求拦截器
axios.interceptors.request.use(config => {// 如果 token 存在, 将用户 token 放入头中(key 是 authorization)if (token) config.headers['authorization'] = tokenreturn config},error => {console.log(error)return Promise.reject(error)}
)// 响应拦截器
axios.interceptors.response.use(function (response) {// 判断执行结果if (!response.data.success) {return Promise.reject(response.data.errorMsg)}return response.data;
}, function (error) {// 一般是服务端异常或者网络异常console.log(error)if (error.response.status == 401) {// 未登录,跳转setTimeout(() => {location.href = "/login.html"}, 200);return Promise.reject("请先登录");}return Promise.reject("服务器异常");
});// 请求参数序列化
axios.defaults.paramsSerializer = function (params) {let p = "";Object.keys(params).forEach(k => {if (params[k]) {p = p + "&" + k + "=" + params[k]}})return p;
}
🎶在 axios 的请求拦截器中配置 token,每次发起请求该请求会首先被拦截
🎶被拦截之后,往该请求的请求头中设置 token【key: authorization;value: token 值】
(3) 短信验证码
Redis 相关常量:
public class RedisConstants {public static final String LOGIN_CODE_KEY = "login:code:phone";public static final Long LOGIN_CODE_TTL = 2L; // 验证码有效期public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 36000L;public static final Long CACHE_NULL_TTL = 2L;public static final Long CACHE_SHOP_TTL = 30L;public static final String CACHE_SHOP_KEY = "cache:shop:";public static final String LOCK_SHOP_KEY = "lock:shop:";public static final Long LOCK_SHOP_TTL = 10L;public static final String SECKILL_STOCK_KEY = "seckill:stock:";public static final String BLOG_LIKED_KEY = "blog:liked:";public static final String FEED_KEY = "feed:";public static final String SHOP_GEO_KEY = "shop:geo:";public static final String USER_SIGN_KEY = "sign:";
}
public Result sendCode(String phone, HttpSession session) {// 校验手机号格式是否符合手机号的规范if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");}// 生成 6 位数字短信验证码String code = RandomUtil.randomNumbers(6);// 把短信验证码保存到 Redis 中stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone,code,RedisConstants.LOGIN_CODE_TTL,TimeUnit.MINUTES);// 发送短信验证码给手机号 phonelog.info("向 {} 手机号发送了验证码:{}", phone, code);return Result.ok("发送验证码成功");}
(4) 短信验证码登录、注册
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {Result result = requestParamsValidate(loginForm, session);if (!result.getSuccess()) { // 手机号或验证码校验失败return result;}// 根据手机号查询用户String phone = loginForm.getPhone();User user = query().eq("phone", phone).one();// 根据手机号在数据中没有查询到用户信息if (user == null) {// 注册用户Result saveResult = saveUserByPhone(phone);if (saveResult.getSuccess()) { // 注册用户成功user = (User) saveResult.getData();} else {return saveResult;}}// 保存用户信息到 Redis// 生成随机 token 串String token = RedisConstants.LOGIN_USER_KEY + UUID.randomUUID().toString(true);// 把 User 转换为 UserDTO(过滤敏感数据)UserDTO userDTO = user2UserDto(user);// 把 UserDTO 转换为 HashMap(便于往 Redis 中存储)Map<String, Object> userDtoMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 把用户信息保存到 Redis// stringRedisTemplate 要求存储的 key 和 value 都必须是 String 类型// 否则会报类型转换错误stringRedisTemplate.opsForHash().putAll(token, userDtoMap);// 设置登录有效期stringRedisTemplate.expire(token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);session.setAttribute("user", user);return Result.ok(token);}private UserDTO user2UserDto(User user) {UserDTO userDTO = new UserDTO();userDTO.setIcon(user.getIcon());userDTO.setId(user.getId());userDTO.setNickName(user.getNickName());return userDTO;}private Result saveUserByPhone(String phone) {User newUser = new User();newUser.setNickName("USER_" + RandomUtil.randomString(9));newUser.setPhone(phone);if (save(newUser)) {return Result.ok(newUser);}return Result.fail("服务器忙, 用户保存到数据库失败");}private Result requestParamsValidate(LoginFormDTO loginForm, HttpSession session) {// 校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");}// 通过手机号获取验证码String redisCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);// 校验验证码是否存在String paramCode = loginForm.getCode();if (RegexUtils.isCodeInvalid(paramCode) || !paramCode.equals(redisCode)) {return Result.fail("验证码错误");}return Result.ok();}}
(5) 免登录
🎄 需要实现一个效果:用户只有在使用该应用,哪么该用户的登录有效期就延长七天
🎄 而不是到达七天就退出登录
🎄 在 LoginInterceptor 中,若用户有发请求,就把该用户的登录有些时间的缓存更新为七天
/*** 登录校验拦截器*/
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {// 获取请求头中的 token 串String token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {response.setStatus(401);return false; // 拦截}// 通过前端传过来的 token 串从 Redis 中获取用户信息Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(token);if (userMap.isEmpty()) {response.setStatus(401);return false; // 拦截}// 保存用户信息到 ThreadLocal 中// 把 HashMap 类型转换为 UserDto 类型UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);UserHolder.saveUser(userDTO);// 刷新登录有效期(只要用户是活跃的, 登录有效期就延长七天)stringRedisTemplate.expire(token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}/*** 资源释放*/@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception {UserHolder.removeUser();}@Overridepublic void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {}
}
🎄 LoginInterceptor 拦截器并没有被 IoC 管理,所以不能在 LoginInterceptor 中使用
@Resource
和@Autowired
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 配置拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns( // 这些请求不可拦截"/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
(6) 刷新登录有效期
🎄 RefreshInterceptor 会拦截每一个请求,然后进行登录有效期刷新
🎄 上一节中的 LoginInterceptor 只有当访问需要登录校验的请求的时候才会刷新
/*** 刷新登录拦截器(每个请求都要来到这里)*/
public class RefreshLoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshLoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {// 获取请求头中的 token 串String token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true; // 拦截(进入 LoginInterceptor)}// 通过前端传过来的 token 串从 Redis 中获取用户信息Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(token);if (userMap.isEmpty()) {return true; // 拦截(进入 LoginInterceptor)}// 保存用户信息到 ThreadLocal 中// 把 HashMap 类型转换为 UserDto 类型UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);UserHolder.saveUser(userDTO);// 刷新登录有效期(只要用户是活跃的, 登录有效期就延长七天)stringRedisTemplate.expire(token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}/*** 资源释放*/@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception {UserHolder.removeUser();}@Overridepublic void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {}
}
/*** 登录校验拦截器*/
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {if (UserHolder.getUser() == null) {response.setStatus(401);return false;}return true;}}
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 配置拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns( // 这些请求不可拦截"/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");// order 值越小优先级越高// order 值默认是 0registry.addInterceptor(new RefreshLoginInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(-1);}
}
相关文章:

【Redis】2、Redis应用之【根据 Session 和 Redis 进行登录校验和发送短信验证码】
目录 一、基于 Session 实现登录(1) 发送短信验证码① 手机号格式后端校验② 生成短信验证码 (2) 短信验证码登录、注册(3) 登录验证① 通过 SpringMVC 定义拦截器② ThreadLocal (4) 集群 Session 不共享问题 二、基于 Redis 实现共享 session 登录(1) 登录之后,缓…...

MiniGPT4系列之二推理篇命令行方式:在RTX-3090 Ubuntu服务器推理详解
MiniGPT4系列之一部署篇:在RTX-3090 Ubuntu服务器部署步骤详解_seaside2003的博客-CSDN博客 MiniGPT4系列之二推理篇命令行方式:在RTX-3090 Ubuntu服务器推理详解_seaside2003的博客-CSDN博客 MiniGPT4系列之三模型推理 (Web UI):在RTX-309…...

Android TvSettings Bug: 密码框无法点击唤起输入法
概述 Android 10 的Box方案, 默认使用的是TvSettings作为系统设置,输入操作的习惯上是使用鼠标,键盘,遥控,日常的场景是没有问题,也不会出现本文中提及的问题。当外接的USB触摸屏后,出现无法点击…...
Windows, MacOS还是Linux好?
今天我们来聊一个小话题:选操作系统,是哪个好?今天,我以一个介绍者的身份给大家推荐,我就不出什么点子了。 Windows Windows,是一个老牌的操作系统。他的优处和短处和有很多,我们来介绍一下 优…...

Gateway自定义过滤器——全局过滤器
一、什么是全局过滤器🍉 首先,我们要知道全局过滤器其实是特殊路由过滤器(特殊的GatewayFilter),会有条件地作用于所有路由。 为什么要自定义全局过滤器?就好比是看大门的保安大叔,平时主要是做好进出大门外来人员登记…...

Android App的几个核心概念
Application启动 点击桌面图标启动App(如下流程图) 针对以上流程图示: ActivityManagerService#startProcessLocked()Process#start()ActivityThread#main(),入口分析的地方ActivityThread#attach(),这个里面的逻辑很核心 ActivityManagerS…...

window10安装telnet
1、打开控制面板 2、点击程序和功能 3、点击启用或关闭Windows功能 4、选中Telnet客户端,然后点击确定,然后就可以使用telnent 主机 端口来查看本地是否能连通该主机的该端口。...
大厂sql真题讲解(黑马)
2023 http://yun.itheima.com/open/853.html | 面试宝典-如何备战大厂SQL真题 http://yun.itheima.com/open/858.html | 面试宝典–大厂必考知识开窗函数 http://yun.itheima.com/open/864.html | 面试宝典-详解美团SQL真题 http://yun.itheima.com/open/868.html | 图解大…...

SpringBoot整合EasyExcel实现读操作
😊 作者: 一恍过去 💖 主页: https://blog.csdn.net/zhuocailing3390 🎊 社区: Java技术栈交流 🎉 主题: SpringBoot整合EasyExcel实现读操作 ⏱️ 创作时间: 2023年…...

go mod 设置国内源 windows 环境 win10
启用 go module 功能 go env -w GO111MODULEon 配置 goproxy 变量 go env -w GOPROXYhttps://goproxy.cn,direct 下载包就行了,速度飞快 go mod tidy 检测 goproxy 是否配置好 运行 go env | findstr goproxy 查看 goproxy Go module 从 Go v1.12 版本开始存在&a…...
智能决策支持系统实现的关键技术分析
1.模 型 中 的 关 键 因 素 。 在 按 本 模 型 研 究 开 发 系 统 时 ,应 当 着 重 考 虑 以 下 几 个 因 素 : (1)设 备 保 护 需 求 计 划 。 保 护 需 求 包 括 了 人 员 、 物 质 、 财 务 等 各项 因 素 ; (2)考 虑 设 备 运行 及维修的 历史数据。 这是进 行 模 型 选 择…...

OSS对象存储后端实现+Vue实现图片上传【基于若依管理系统开发】
文章目录 基本介绍术语介绍图片上传方式介绍普通上传用户直传应用服务器签名后直传 OSS对象存储后端实现maven配置文件配置类ServiceController 图片上传前端图片上传组件api页面使用组件组件效果 基本介绍 术语介绍 Bucket(存储空间):用于…...
人工智能学习目录
1、人工智能-电脑如何像人一样思考? 从发展历史到人工智能的应用案例,再到人工智能本质是数学问题,从房价预测问题提出损失函数由参数导致,再由损失函数的最优值入手引入梯度下降法,最后到多参数方程的最优求解。 人工…...
Vue单页面实现el-tree el-breadcrumb功能、el-tree右键点击树节点展示菜单功能、树节点编辑节点字段名称功能
(1) 点击el-tree节点 使用el-breadcrumb展示选中树节点及父项数据 重点:handleNodeClick方法、getTreeNode方法 (2) 选择el-breadcrumb-item设置el-tree节点选中 必须设置属性: current-node-key"currentNodeKey" 、 node-key"id" 重点: 设置…...

C++核心编程之函数高级使用
目录 一、函数的默认参数 二、函数占位参数 三、函数重载 四、函数重载-注意事项 一、函数的默认参数 在C中,函数的形参列表中的形参是可以有默认值的 语法:返回值类型 函数名 (参数默认值){} 示例1: #includ…...

如何创建智能合约游戏系统
区块技术的发展,智能合约成为了一个热门话题。智能合约是一种基于区块技术的自动化合约,它可以自动执行合同中规定的条款,从而实现去中心化的信任和价值传递。在游戏领域,智能合约可以让玩家在游戏中实现各种交易和交互࿰…...

如何用rust实现一个异步channel
目录 前言思路实现功能代码实现 测试先引测试版包测试代码结果与分析思考 尾语 前言 使用通信来共享内存,而不是通过共享内存来通信 上面这句话,是每个go开发者在 处理多线程通信时 的座右铭,go甚至把实现这个理念的channel直接焊在编译器里&…...
gitee上传项目到仓库
目录 一些常用的Git命令切换到其他盘符:列出目录下的所有文件:以树状图的形式显示目录下的文件和子目录:返回上一层目录:写的C#代码文件上传到新建的Git仓库中,可以按照以下步骤进行操作:出现的错误: 一些常…...

day27 贪心算法
1.什么是贪心? 比如10张钞票,有1,5,20,100等面额,取五张,如何取得到数额最多的钱?每次取面额最大的那张钞票;就是每个阶段的局部最优;全局最优就是最后拿到的…...
Java实现字符串反转
起因 自己在刷题的过程中,想把一个字符串翻转一下,便写了下面的代码: String str "abcd";str str.reverse();发现行不通,这是为什么呢? 分析 在Java中,字符串是不可变的对象,这意…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
WEB3全栈开发——面试专业技能点P7前端与链上集成
一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性: ✅ 文件系…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...