当前位置: 首页 > news >正文

【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) 登录之后&#xff0c;缓…...

MiniGPT4系列之二推理篇命令行方式:在RTX-3090 Ubuntu服务器推理详解

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

Android TvSettings Bug: 密码框无法点击唤起输入法

概述 Android 10 的Box方案&#xff0c; 默认使用的是TvSettings作为系统设置&#xff0c;输入操作的习惯上是使用鼠标&#xff0c;键盘&#xff0c;遥控&#xff0c;日常的场景是没有问题&#xff0c;也不会出现本文中提及的问题。当外接的USB触摸屏后&#xff0c;出现无法点击…...

Windows, MacOS还是Linux好?

今天我们来聊一个小话题&#xff1a;选操作系统&#xff0c;是哪个好&#xff1f;今天&#xff0c;我以一个介绍者的身份给大家推荐&#xff0c;我就不出什么点子了。 Windows Windows&#xff0c;是一个老牌的操作系统。他的优处和短处和有很多&#xff0c;我们来介绍一下 优…...

Gateway自定义过滤器——全局过滤器

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

Android App的几个核心概念

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

window10安装telnet

1、打开控制面板 2、点击程序和功能 3、点击启用或关闭Windows功能 4、选中Telnet客户端&#xff0c;然后点击确定&#xff0c;然后就可以使用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实现读操作

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; SpringBoot整合EasyExcel实现读操作 ⏱️ 创作时间&#xff1a; 2023年…...

go mod 设置国内源 windows 环境 win10

启用 go module 功能 go env -w GO111MODULEon 配置 goproxy 变量 go env -w GOPROXYhttps://goproxy.cn,direct 下载包就行了&#xff0c;速度飞快 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&#xff08;存储空间&#xff09;&#xff1a;用于…...

人工智能学习目录

1、人工智能-电脑如何像人一样思考&#xff1f; 从发展历史到人工智能的应用案例&#xff0c;再到人工智能本质是数学问题&#xff0c;从房价预测问题提出损失函数由参数导致&#xff0c;再由损失函数的最优值入手引入梯度下降法&#xff0c;最后到多参数方程的最优求解。 人工…...

Vue单页面实现el-tree el-breadcrumb功能、el-tree右键点击树节点展示菜单功能、树节点编辑节点字段名称功能

(1) 点击el-tree节点 使用el-breadcrumb展示选中树节点及父项数据 重点&#xff1a;handleNodeClick方法、getTreeNode方法 (2) 选择el-breadcrumb-item设置el-tree节点选中 必须设置属性: current-node-key"currentNodeKey" 、 node-key"id" 重点: 设置…...

C++核心编程之函数高级使用

目录 一、函数的默认参数 二、函数占位参数 三、函数重载 四、函数重载-注意事项 一、函数的默认参数 在C中&#xff0c;函数的形参列表中的形参是可以有默认值的 语法&#xff1a;返回值类型 函数名 &#xff08;参数默认值&#xff09;{} 示例1&#xff1a; #includ…...

如何创建智能合约游戏系统

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

如何用rust实现一个异步channel

目录 前言思路实现功能代码实现 测试先引测试版包测试代码结果与分析思考 尾语 前言 使用通信来共享内存&#xff0c;而不是通过共享内存来通信 上面这句话&#xff0c;是每个go开发者在 处理多线程通信时 的座右铭&#xff0c;go甚至把实现这个理念的channel直接焊在编译器里&…...

gitee上传项目到仓库

目录 一些常用的Git命令切换到其他盘符&#xff1a;列出目录下的所有文件&#xff1a;以树状图的形式显示目录下的文件和子目录:返回上一层目录&#xff1a;写的C#代码文件上传到新建的Git仓库中&#xff0c;可以按照以下步骤进行操作&#xff1a;出现的错误&#xff1a; 一些常…...

day27 贪心算法

1.什么是贪心&#xff1f; 比如10张钞票&#xff0c;有1&#xff0c;5&#xff0c;20&#xff0c;100等面额&#xff0c;取五张&#xff0c;如何取得到数额最多的钱&#xff1f;每次取面额最大的那张钞票&#xff1b;就是每个阶段的局部最优&#xff1b;全局最优就是最后拿到的…...

Java实现字符串反转

起因 自己在刷题的过程中&#xff0c;想把一个字符串翻转一下&#xff0c;便写了下面的代码&#xff1a; String str "abcd";str str.reverse();发现行不通&#xff0c;这是为什么呢&#xff1f; 分析 在Java中&#xff0c;字符串是不可变的对象&#xff0c;这意…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...