基于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 存储用户信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/61644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java语言程序设计 选填题知识点总结

第一章 javac.exe是JDK提供的编译器public static void main (String args[])是Java应用程序主类中正确的main方法Java源文件是由若干个书写形式互相独立的类组成的Java语言的名字是印度尼西亚一个盛产咖啡的岛名Java源文件中可以有一个或多个类Java源文件的扩展名是.java如果…

python程序的编写以及发布(形象类比)

最近重新接触python&#xff0c;本人之前对于python的虚拟环境&#xff0c;安装包比较比较迷惑&#xff0c;这里给出一个具象的理解。可以将 Python 程序运行的过程类比成一次 做菜的过程&#xff0c;从准备食材到最后出锅。以下是具体的类比步骤&#xff1a; 1. 安装 Python 环…

shell基础知识3 --- 流程控制之条件判断

条件判断语句是一种最简单的流程控制语句。该语句使得程序根据不同的条件来执行不同的程序分支。 一、if语句语法 1.单分支结构 法1&#xff1a; 法2&#xff1a; if <条件表达式> if…

电容测试流程

一、外观检测 1. 目的&#xff1a;检验电容样品外观是否与规格书一致&#xff0c;制程工艺是否良好&#xff0c;确保部品的品质。 2. 仪器&#xff1a;放大镜 3. 测试说明&#xff1a; &#xff08;1&#xff09;样品上丝印与规格书中相符&#xff0c;丝印信息&#xff08;…

探索 .NET 9 控制台应用中的 LiteDB 异步 CRUD 操作

本文主要是使用异步方式&#xff0c;体验 litedb 基本的 crud 操作。 LiteDB 是一款轻量级、快速且免费的 .NET NoSQL 嵌入式数据库&#xff0c;专为小型本地应用程序设计。它以单一数据文件的形式提供服务&#xff0c;支持文档存储和查询功能&#xff0c;适用于桌面应用、移动…

leetcode刷题记录(四十二)——101. 对称二叉树

&#xff08;一&#xff09;问题描述 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/symmetric-tree/description/给你…

【一个简单的整数问题2——线段树】

题目 代码 下面的两个代码的区别在于modify的分类&#xff0c;modify最简单的分类方式是存在性分类&#xff0c;另一种类似某些query采用的三段式分类&#xff0c;详细见代码 存在性 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1…

从源码到应用:在线教育系统与教培网校APP开发实战指南

时下&#xff0c;各类教培网校APP逐渐成为教育机构的核心工具。那么&#xff0c;如何从源码出发&#xff0c;开发一套符合需求的在线教育系统与教培网校APP&#xff1f;本文将从架构设计、功能实现到部署上线&#xff0c;提供一份全面的开发实战指南。 一、在线教育系统的核心架…

vscode下面python调试报错ImportError: cannot import name ‘Literal‘ from ‘typing‘

1 问题描述 我在vscode下面编写python程序&#xff0c;这个程序是在一个英伟达anoconda环境下的项目。之前能运行能调试&#xff0c;最近发现只能运行ctlf5&#xff0c;但是使用f5进行调试时&#xff0c;报错“File “c:\Users\86137.vscode\extensions\ms-python.debugpy-202…

智能外呼,轻松触达海外客户

在全球化的今天&#xff0c;海外市场已成为众多企业寻求增长的重要阵地。然而&#xff0c;如何高效、精准地触达海外客户&#xff0c;一直是企业面临的一大挑战。沃丰科技推出了智能外呼机器人&#xff0c;为企业打开了一扇通往海外市场的智慧之门。 沃丰科技外呼机器人的核心…

小鹏汽车智慧材料数据库系统项目总成数据同步

1、定时任务处理 2、提供了接口 小鹏方面提供的推送的数据表结构&#xff1a; 这几个表总数为100多万&#xff0c;经过条件筛选过滤后大概2万多条数据 小鹏的人给的示例图&#xff1a; 界面&#xff1a; SQL: -- 查询车型 select bmm.md_material_id, bmm.material_num, bm…

Parker派克防爆电机在实际应用中的安全性能如何保证?

Parker防爆电机确保在实际应用中的安全性能主要通过以下几个方面来保证&#xff1a; 1.防爆外壳设计&#xff1a;EX系列电机采用强大的防爆外壳&#xff0c;设计遵循严格的防爆标准&#xff0c;能够承受内部可能发生的爆炸而不破损&#xff0c;利用间隙切断原理&#xff0c;防…

如何在Word文件中设置水印以及如何禁止修改水印

在日常办公和学习中&#xff0c;我们经常需要在Word文档中设置水印&#xff0c;以保护文件的版权或标明文件的机密性。水印可以是文字形式&#xff0c;也可以是图片形式&#xff0c;能够灵活地适应不同的需求。但仅仅设置水印是不够的&#xff0c;有时我们还需要确保水印不被随…

Linux高阶——1123—

1、服务器基础 1、服务器基本概述 在CS架构下&#xff0c;client and server下&#xff0c;工程师研发服务器&#xff0c;经典的后端程序&#xff0c;为前端&#xff08;客户端&#xff09;提供数据处理支持、数据中转、数据持久化等功能&#xff0c;在互联网中&#xff0c;几…

鸿蒙NEXT开发案例:字数统计

【引言】 本文将通过一个具体的案例——“字数统计”组件&#xff0c;来探讨如何在鸿蒙NEXT框架下实现这一功能。此组件不仅能够统计用户输入文本中的汉字、中文标点、数字、以及英文字符的数量&#xff0c;还具有良好的用户界面设计&#xff0c;使用户能够直观地了解输入文本…

贪心算法(1)

目录 柠檬水找零 题解&#xff1a; 代码&#xff1a; 将数组和减半的最少操作次数&#xff08;大根堆&#xff09; 题解&#xff1a; 代码&#xff1a; 最大数&#xff08;注意 sort 中 cmp 的写法&#xff09; 题解&#xff1a; 代码&#xff1a; 摆动序列&#xff0…

linux从0到1——shell编程7

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

微软发布Win11 24H2系统11月可选更新KB5046740!

系统之家11月22日报道&#xff0c;微软针对Win11 24H2系统推出2024年11月最新可选更新补丁KB5046740&#xff0c;更新后系统版本后升至26100.2454&#xff0c;此次更新后修复当应用程序以PDF和XLSX格式导出图表对象时停止响应、无法使用API查找旋转信息等问题。以下小编将给大家…

五天SpringCloud计划——DAY2之使用Docker完成项目的部署

一、引言 刚刚学完了Docker的使用&#xff0c;现在知识在脑子里面还是热乎的&#xff0c;是时候把它总结一下了。 现在的我认为Docker时一个部署项目的工具(不知道是不是真的),相比于我以前使用宝塔面板部署项目&#xff0c;使用Docker更能让我看到代码之美&#xff0c;怎么一…

设计模式之 模板方法模式

模板方法模式是行为型设计模式的一种。它定义了一个算法的骨架&#xff0c;并将某些步骤的实现延迟到子类中。模板方法模式允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。 模板方法模式的核心在于&#xff1a; 封装算法的骨架&#xff1a;通过父类中的模板方…