基于Redis实现的手机短信登入功能
目录
开发准备
注册阿里短信服务
依赖坐标
阿里短信 依赖
mybatis-plus 依赖
redis 依赖
配置文件
导入数据库表
短信发送工具类
生成随机验证码的工具类
校验合法手机号的工具类
ThreadLocal 线程工具类
消息工具类
基于 session 的短信登录的问题
开发教程
Redis 结构设计
实现效果
代码地址:手机短信认证
开发准备
注册阿里短信服务
首先我们需要获取阿里的短信服务,进入之后点击国内消息按照流程申请模板即可。详细教程可以参考这篇文章:如何注册阿里短信服务
依赖坐标
阿里短信 依赖
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.16</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>2.1.0</version></dependency>
mybatis-plus 依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency>
redis 依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.4.0</version>
</dependency>
配置文件
server:port: 8081
spring:application:name: hmdpdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/hmdp?useSSL=false&useUnicode=true&characterEncoding=utf8username: rootpassword:redis:host: localhostport: 6379password:lettuce:pool:max-active: 10max-idle: 10min-idle: 1time-between-eviction-runs: 10sjackson:default-property-inclusion: non_null # JSON 处理时忽略非空字段
mybatis-plus:type-aliases-package: com.hmdp.entity # 别名扫描包
logging:level:com.hmdp: debug # 打印日志
注意数据库的信息需要填成你自己的。
导入数据库表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号码',`password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码,加密存储',`nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '昵称,默认是用户id',`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '人物头像',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uniqe_key_phone`(`phone`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1010 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
短信发送工具类
/*** 短信发送工具类*/
public class SMSUtils {/*** 发送短信* @param signName 签名* @param templateCode 模板* @param phoneNumbers 手机号* @param param 参数*/public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){// 此处需要替换成开发者自己的(在阿里云访问控制台寻找)DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "自己的AccessKeyId", "自己的accesskeySecret");IAcsClient client = new DefaultAcsClient(profile);SendSmsRequest request = new SendSmsRequest();request.setSysRegionId("cn-hangzhou");// 要发送给那个人的电话号码request.setPhoneNumbers(phoneNumbers);// 我们在阿里云设置的签名request.setSignName(signName);// 我们在阿里云设置的模板request.setTemplateCode(templateCode);// 在设置模板的时候有一个占位符request.setTemplateParam("{\"code\":\""+param+"\"}");try {SendSmsResponse response = client.getAcsResponse(request);System.out.println("短信发送成功");}catch (ClientException e) {e.printStackTrace();}}}
生成随机验证码的工具类
public class RandomUtil {public static final String BASE_NUMBER = "0123456789";public static final String BASE_CHAR = "abcdefghijklmnopqrstuvwxyz";public static final String BASE_CHAR_NUMBER = "abcdefghijklmnopqrstuvwxyz0123456789";public RandomUtil() {}public static ThreadLocalRandom getRandom() {return ThreadLocalRandom.current();}public static SecureRandom createSecureRandom(byte[] seed) {return null == seed ? new SecureRandom() : new SecureRandom(seed);}public static SecureRandom getSecureRandom() {return getSecureRandom((byte[])null);}public static SecureRandom getSecureRandom(byte[] seed) {return createSecureRandom(seed);}public static SecureRandom getSHA1PRNGRandom(byte[] seed) {SecureRandom random;try {random = SecureRandom.getInstance("SHA1PRNG");} catch (NoSuchAlgorithmException var3) {NoSuchAlgorithmException e = var3;throw new UtilException(e);}if (null != seed) {random.setSeed(seed);}return random;}public static SecureRandom getSecureRandomStrong() {try {return SecureRandom.getInstanceStrong();} catch (NoSuchAlgorithmException var1) {NoSuchAlgorithmException e = var1;throw new UtilException(e);}}public static Random getRandom(boolean isSecure) {return (Random)(isSecure ? getSecureRandom() : getRandom());}public static boolean randomBoolean() {return 0 == randomInt(2);}public static char randomChinese() {return (char)randomInt(19968, 40959);}public static int randomInt(int min, int max) {return getRandom().nextInt(min, max);}public static int randomInt() {return getRandom().nextInt();}public static int randomInt(int limit) {return getRandom().nextInt(limit);}public static long randomLong(long min, long max) {return getRandom().nextLong(min, max);}public static long randomLong() {return getRandom().nextLong();}public static long randomLong(long limit) {return getRandom().nextLong(limit);}public static double randomDouble(double min, double max) {return getRandom().nextDouble(min, max);}public static double randomDouble(double min, double max, int scale, RoundingMode roundingMode) {return NumberUtil.round(randomDouble(min, max), scale, roundingMode).doubleValue();}public static double randomDouble() {return getRandom().nextDouble();}public static double randomDouble(int scale, RoundingMode roundingMode) {return NumberUtil.round(randomDouble(), scale, roundingMode).doubleValue();}public static double randomDouble(double limit) {return getRandom().nextDouble(limit);}public static double randomDouble(double limit, int scale, RoundingMode roundingMode) {return NumberUtil.round(randomDouble(limit), scale, roundingMode).doubleValue();}public static BigDecimal randomBigDecimal() {return NumberUtil.toBigDecimal(getRandom().nextDouble());}public static BigDecimal randomBigDecimal(BigDecimal limit) {return NumberUtil.toBigDecimal(getRandom().nextDouble(limit.doubleValue()));}public static BigDecimal randomBigDecimal(BigDecimal min, BigDecimal max) {return NumberUtil.toBigDecimal(getRandom().nextDouble(min.doubleValue(), max.doubleValue()));}public static byte[] randomBytes(int length) {byte[] bytes = new byte[length];getRandom().nextBytes(bytes);return bytes;}public static <T> T randomEle(List<T> list) {return randomEle(list, list.size());}public static <T> T randomEle(List<T> list, int limit) {if (list.size() < limit) {limit = list.size();}return list.get(randomInt(limit));}public static <T> T randomEle(T[] array) {return randomEle(array, array.length);}public static <T> T randomEle(T[] array, int limit) {if (array.length < limit) {limit = array.length;}return array[randomInt(limit)];}public static <T> List<T> randomEles(List<T> list, int count) {List<T> result = new ArrayList(count);int limit = list.size();while(result.size() < count) {result.add(randomEle(list, limit));}return result;}public static <T> List<T> randomEleList(List<T> source, int count) {if (count >= source.size()) {return ListUtil.toList(source);} else {int[] randomList = ArrayUtil.sub(randomInts(source.size()), 0, count);List<T> result = new ArrayList();int[] var4 = randomList;int var5 = randomList.length;for(int var6 = 0; var6 < var5; ++var6) {int e = var4[var6];result.add(source.get(e));}return result;}}public static <T> Set<T> randomEleSet(Collection<T> collection, int count) {ArrayList<T> source = CollUtil.distinct(collection);if (count > source.size()) {throw new IllegalArgumentException("Count is larger than collection distinct size !");} else {Set<T> result = new LinkedHashSet(count);int limit = source.size();while(result.size() < count) {result.add(randomEle((List)source, limit));}return result;}}public static int[] randomInts(int length) {int[] range = ArrayUtil.range(length);for(int i = 0; i < length; ++i) {int random = randomInt(i, length);ArrayUtil.swap(range, i, random);}return range;}public static String randomString(int length) {return randomString("abcdefghijklmnopqrstuvwxyz0123456789", length);}public static String randomStringUpper(int length) {return randomString("abcdefghijklmnopqrstuvwxyz0123456789", length).toUpperCase();}public static String randomStringWithoutStr(int length, String elemData) {String baseStr = "abcdefghijklmnopqrstuvwxyz0123456789";baseStr = StrUtil.removeAll(baseStr, elemData.toCharArray());return randomString(baseStr, length);}public static String randomNumbers(int length) {return randomString("0123456789", length);}public static String randomString(String baseString, int length) {if (StrUtil.isEmpty(baseString)) {return "";} else {StringBuilder sb = new StringBuilder(length);if (length < 1) {length = 1;}int baseLength = baseString.length();for(int i = 0; i < length; ++i) {int number = randomInt(baseLength);sb.append(baseString.charAt(number));}return sb.toString();}}public static char randomNumber() {return randomChar("0123456789");}public static char randomChar() {return randomChar("abcdefghijklmnopqrstuvwxyz0123456789");}public static char randomChar(String baseString) {return baseString.charAt(randomInt(baseString.length()));}/** @deprecated */@Deprecatedpublic static Color randomColor() {Random random = getRandom();return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));}public static <T> WeightRandom<T> weightRandom(WeightRandom.WeightObj<T>[] weightObjs) {return new WeightRandom(weightObjs);}public static <T> WeightRandom<T> weightRandom(Iterable<WeightRandom.WeightObj<T>> weightObjs) {return new WeightRandom(weightObjs);}public static DateTime randomDay(int min, int max) {return randomDate(DateUtil.date(), DateField.DAY_OF_YEAR, min, max);}public static DateTime randomDate(Date baseDate, DateField dateField, int min, int max) {if (null == baseDate) {baseDate = DateUtil.date();}return DateUtil.offset((Date)baseDate, dateField, randomInt(min, max));}
}
校验合法手机号的工具类
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);}
}
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();}
}
消息工具类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {private Boolean success;private String errorMsg;private Object data;private Long total;public static Result ok(){return new Result(true, null, null, null);}public static Result ok(Object data){return new Result(true, null, data, null);}public static Result ok(List<?> data, Long total){return new Result(true, null, data, total);}public static Result fail(String errorMsg){return new Result(false, errorMsg, null, null);}
}
基于 session 的短信登录的问题
多台 tomcat 并不共享 session 存储空间,当请求切换到不同服务器会导致数据丢失问题。虽然可以用 tomcat 间的数据同步解决这个问题,但还是会出现数据不一致和占用内存问题。
session 代替方案应该满足:
数据共享 |
内存存储 |
key,value结构 |
使用 redis 代替 session 是完全可以的
开发教程
Redis 结构设计
(1)之前的生成验证码后,我们会将其存放在 Session 中,每一个不同的请求就会对应一个 Session,它们 SessionID 肯定是唯一的。因为我们的程序上线面对的是庞大用户量。在高并发的场景下,生成的验证码是有可能相同的,如果使用验证码作为 key,那么就会造成数据覆盖。
所以我们使用手机号作为key,验证码作为value,这样保证key的唯一性。从而保证我们的数据安全。
(2)登入注册时创建用户,用户信息需要保存到 Redis 中,此时的 key 建议用一个随机的 token(建议不要用手机号码,因为之后这个 token 会传入前端,有泄露风险),value 存放用户信息。所以这里返回 token 给客户端,为的就是之后在校验的时候可以拿着 token 去 Redis 中取数据进行校验。
(3)之前的登入校验是 Session 与 Cookie,当使用了 Session,它会自动将 SessionID 传入 Cookie 中,每次请求都会携带 Cookie,就相当于带着 SessionID 去查找用户。
而现在是用请求中携带的 token 去 Redis 中获取用户数据。
(4)
Redis 中的 String 与 Hash 结构都能对用户信息进行存储;但是考虑到内存的占用,Hash 结构无疑是最好的。
(5)Redis 是基于内存的,而我们知道内存的空间是十分有限的;那么用户退出系统了还需要保留用户的 token 吗。答案肯定是不需要的。那么我们就需要设置 token 的有效期。我们不能设置了 token 有效期就高枕无忧了,还需要判断当前用户是否还在使用。如果用户还在使用,而你的 token 有限期恰恰失效了,那么这就是一个不合格的系统。我们可以使用一个拦截器判断当前用户是否还在使用,如果正在使用就刷新 token。
Controller
/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone,session);}/*** 登录功能* @param 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm,session);}
Service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 校验手机号if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机格式错误");}// 校验符合,生成验证码String code = RandomUtil.randomNumbers(6);Map<String,Object> param = new HashMap<>();param.put("code", code);// 保存验证码到 redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 发送验证码log.debug("发送验证码成功,验证码为:"+code);String templateCode = "【验证码】您的验证码为:" + code + "。3分钟内有效,请勿泄露和转发。如非本人操作,请忽略此短信。";SMSUtils.sendMessage("东方",templateCode,phone,code);// 返回成功return Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机格式错误");}// 输入验证码String code = loginForm.getCode();// 生成的验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);// 校验验证码if(cacheCode == null || !cacheCode.equals(code)){// 验证码不一致return Result.fail("验证码不一致");}// 如果一致,判断用户是否存在User user = query().eq("phone", phone).one();// 用户不存在if(user == null){// 创建一个新用户,并保存用户信息user = createUserWithPhone(phone);}// 随机生成 token 作为登入令牌String token = UUID.randomUUID().toString();// 将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()));// 存储String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);// 设置 token 的有效期stringRedisTemplate.expire(tokenKey,CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(token);}
}
拦截器
刷新拦截器
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2.基于 token 获取 redis 中的用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}
登入拦截器
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}
}
简单说一下刷新功能:
假设此时用户的 token 失效了,那么就会被刷新拦截器拦截,因为当前用户是在线状态。所以前两条 return 都不会返回,直到刷新了有效期才能返回。
配置拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login").order(1);// token 刷新的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}
当项目中存在多个拦截器,那么的执行顺序是根据方法而定的,拦截器所有的 preHandle 方法是按照拦截器的 order 升序执行的,如果 order 一致,则按照添加顺序执行。
静态变量
public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 3L;public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 30L;
实现效果
手机号验证登入
Redis 存储到验证码
Redis 存储用户信息
相关文章:

基于Redis实现的手机短信登入功能
目录 开发准备 注册阿里短信服务 依赖坐标 阿里短信 依赖 mybatis-plus 依赖 redis 依赖 配置文件 导入数据库表 短信发送工具类 生成随机验证码的工具类 校验合法手机号的工具类 ThreadLocal 线程工具类 消息工具类 基于 session 的短信登录的问题 开发教程 Redis 结构设计 …...

C# NetworkStream用法
一、注意事项: NetworkStream 是稳定的,面向连接的,所以它只适合 TCP 协议的环境下工作所以一旦在 UDP环境中,虽然编译不会报错,但是会跳出异常。如果用构造产生NetworkStream的实例,则必须使用连接的Socke…...

华三预赛从零开始学习笔记(每日编辑,复习完为止)
知识点分布 路由交换技术基础 计算机网络基本概念 计算机网络基本概念: 很多电脑和设备通过电线或无线信号连在一起,可以互相“说话”和“分享东西” 网络的主要形式和发展历程: 诞生阶段-最早的计算机网络是以单个计算机为中心的联机系统-终…...

MySQL基础大全(看这一篇足够!!!)
文章目录 前言一、初识MySQL1.1 数据库基础1.2 数据库技术构成1.2.1 数据库系统1.2.2 SQL语言1.2.3 数据库访问接口 1.3 什么是MySQL 二、数据库的基本操作2.1 数据库创建和删除2.2 数据库存储引擎2.2.1 MySQL存储引擎简介2.2.2 InnoDB存储引擎2.2.3 MyISAM存储引擎2.2.4 存储引…...

[ 应急响应进阶篇-2 ] Linux创建后门并进行应急处置-1:超级用户帐号后门
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...

【无人机/平衡车/机器人】详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波
详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波 效果: 更多单片机项目,单片机项目合集列表目录与专栏说明: 单片机项目合集列表与专栏说明——Excel合集列表目录查阅(持续更新)-CSDN博客编辑https://archie.blog.csdn.net/article/details/142381401https:/…...

数据结构-8.Java. 七大排序算法(上篇)
本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 上篇主要实现 前四种排序算法: 直接插入, 希尔, 选择, 堆排。 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 …...

YOLOV5/rknn生成可执行文件部署在RK3568上
接上一篇文章best-sim.rknn模型生成好后,我们要将其转换成可执行文件运行在RK3568上,这一步需要在rknpu上进行,在强调一遍!!rknpu的作用是可以直接生成在开发板上运行的程序 退出上一步的docker环境 exit1.复制best-…...

java http body的格式 application/x-www-form-urlencoded不支持文件上传
在Java中,HTTP请求的body部分可以包含多种格式的数据,主要包括以下几种: application/x-www-form-urlencoded:这种格式将数据编码成键值对的形式,键和值都进行了URL编码,键值对之间用&符号连接。…...

GPU服务器厂家:为什么要选择 GPU 服务器?
文章来源于百家号:GPU服务器厂家 嘿,各位小伙伴们!今天咱来聊聊为啥要选择 GPU 服务器,特别是定制化的那种哦。 你们知道吗?现在定制化 GPU 服务器那可是超火的,简直就是科研项目的超强 “外挂”&#x…...

Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作(三)
Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作(三) py2neo 删除 1、连接数据库 from py2neo import Graph graph Graph("bolt://xx.xx.xx.xx:7687", auth(user, pwd), nameneo4j)2、删除节点 # 删除单个节点 node graph.node…...

Idea忽略提交文件、Idea设置文件隐藏、Idea提交时隐藏部分文件、git提交时忽略文件
文章目录 一、在idea中commit文件时隐藏文件方式一:创建.gitignore文件(推荐)方式二:通过File Types设置隐藏文件方式三:通过Git配置忽略文件(不推荐)总结 二、可能遇到的问题2.1、.gitigno…...

python如何使用spark操作hive
文章目录 1、服务启动2、修改配置3、验证4、开发环境编写代码操作hive 1、服务启动 # 启动hdfs和yarn start-all.sh # 日志服务也需要启动一下 mapred --daemon start historyserver # 启动spark的日志服务 /opt/installs/spark/sbin/start-history-server.sh #启动hive的meta…...

观察者模式和订阅模式
观察者模式和订阅模式在概念上是相似的,它们都涉及到一个对象(通常称为“主题”或“发布者”)和多个依赖对象(称为“观察者”或“订阅者”)之间的关系。然而,尽管它们有相似之处,但在某些方面也…...

基于ToLua的C#和Lua内存共享方案保姆级教程
C#和Lua内存共享方案保姆级教程 前言 在介绍C#和Lua内存共享方案之前,先介绍下面两个点来支撑这个方案的必要性 跨语言交互很费 Lua和C#交互最早是基于反射的方式实现的,后来为了提升性能发展成Luajit+C#静态方法导出注入到lua虚拟机的方式至此Lua+Unity的性能才达到了实…...

OpenCV与AI深度学习|16个含源码和数据集的计算机视觉实战项目(建议收藏!)
本文来源公众号“OpenCV与AI深度学习”,仅用于学术分享,侵权删,干货满满。 原文链接:分享|16个含源码和数据集的计算机视觉实战项目 本文将分享16个含源码和数据集的计算机视觉实战项目。具体包括: 1. 人…...

Vue 如何简单更快的对 TypeScript 中接口的理解?应用场景?
TypeScript 中接口(Interface)的理解与应用 在 TypeScript 中,接口(Interface) 是一种用来定义对象的结构或形状的方式。接口可以指定对象中应该包含哪些属性、这些属性的类型以及它们的函数签名。接口帮助我们在代码…...

R语言绘图过程中遇到图例的图块中出现字符“a“的解决方法
R语言绘图过程中遇到图例的图块中出现字符的解决方法 因为我遇到这个问题的时候没在网上找到合适的方法,找到个需要付费的,算了。也许是因为问的方式不同,问了半天AI也回答出来,莫名有些烦躁,打算对代码做个分析&…...

视图合并机制解析 | OceanBase查询优化
背景 在默认配置下,若查询语句中嵌入了视图,系统会先等待视图内部所包含的查询完全执行完成后,再继续执行父查询。这种方式造成优化器无法将视图查询与外层查询视为一个整体来进行优化处理,从而限制了优化效果。因此,…...

sql注入报错分享(mssql+mysql)
mysql mysql的报错内容比较多 网上也有比较多的 这里重复的就不多介绍了。一笔带过 溢出类 bigint 当超过mysql的整形的时候,就会导致溢出,mysql可能会将错误信息带出。这里user()是字母默认为0 取反以后1可能就会导致异常。 报错特征 BIGINT UNSIG…...

PHP 高并发解决方案
PHP作为一种脚本语言,在处理高并发请求时可能面临一些挑战。但通过合理的设计和优化,可以有效提升PHP应用程序的性能和并发处理的能力。 一、缓存 页面缓存:将生成的页面缓存起来,减少对数据库的查询,提高响应速度。…...

k8s1.30.0高可用集群部署
负载均衡 nginx负载均衡 两台nginx负载均衡 vim /etc/nginx/nginx.conf stream {upstream kube-apiserver {server 192.168.0.11:6443 max_fails3 fail_timeout30s;#server 192.168.0.12:6443 max_fails3 fail_timeout30s;#server 192.168.0.13:6443 max_fails3…...

多摩川编码器协议及单片机使用
参考: https://blog.csdn.net/qq_28149763/article/details/132718177 https://mp.weixin.qq.com/s/H4XoR1LZSMH6AxsjZuOw6g 1、多摩川编码器协议 多摩川数据通讯是基于485 硬件接口标准NRZ 协议,通讯波特率为2.5Mbps 的串行通讯,采用差分两…...

Android 网络通信(三)OkHttp实现登入
学习笔记 目录 一. 先写XML布局 二、创建 LoginResponse 类 :封装响应数据 目的和作用: 三、创建 MyOkHttp 类 :发送异步请求 代码分析 可能改进的地方 总结 四、LoginActivity 类中实现登录功能 详细分析与注释: 总结: 改进建议: 零、响应数据样例 通过 P…...

分享一下arr的意义(c基础)(必看)(牢记)
arr 即数组名 一般指数组首元素地址 在两种情况下不是 1:sizeof(arr) arr指整个数组简单讲解一下strlen与sizeof(c基础)_strzeof在c语言中什么意思-CSDN博客 2:printf("%p",&…...

AGENT AI 综述核心速览
研究背景 研究问题:这篇文章探讨了多模态人工智能(Agent AI)系统在理解和响应视觉和语言输入方面的潜力,特别是在物理和虚拟环境中的应用。Agent AI旨在通过感知和行动来增强人工智能系统的交互性和适应性。研究难点:…...

基于Java Springboot房屋租赁系统
一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据…...

力扣 LeetCode 701. 二叉搜索树中的插入操作(Day10:二叉树)
解题思路: 全部插入到叶子节点即可 class Solution {public TreeNode insertIntoBST(TreeNode root, int val) {if (root null) {TreeNode node new TreeNode(val);return node;}if (root.val < val) {root.right insertIntoBST(root.right, val);}if (root…...

猎板科技:PCB 特殊定制领域的卓越引领者
一、专业团队,创新设计之源 猎板科技的核心竞争力首先源于其卓越的专业团队。这支队伍汇聚了经验丰富的资深工程师以及行业前沿的技术专家,他们在 PCB 设计领域拥有深厚的造诣和敏锐的洞察力。无论是面对常规 PCB 设计任务,还是应对极具挑战…...

centos stream 9安装docker教程
第一步:安装该dnf-plugins-core软件包(它提供了管理 DNF 存储库的命令) sudo dnf -y install dnf-plugins-core 第二步:设置存储库(这里使用的是阿里云的镜像源) sudo dnf config-manager --add-repo https://mirrors.aliyun.c…...