黑马点评part1 -- 短信登录

目录

 1 . 导入项目 : 

2 . 基于Session实现短信验证登录

2 . 1 原理 : 

2 . 2 发送短信验证码 : 

2 . 3 短信验证码登录和验证功能 :

2 . 4 登录验证功能

2 . 5 隐藏用户敏感信息

2 . 6 session共享问题

2 . 7 Redis 代替 session

2 . 8 基于Redis实现短信登录

UserServiceImpl

发送短信验证码 : 

用户登录 : 

LoginInterceptor : 

报错 : 

测试 : 

2 . 9 登录拦截器的优化 


 1 . 导入项目 : 

先导入sql文件 : 

导入后端项目 : 

注意要修改一些地方 : 

1 . mysql配置,要改成自己的 : 

如果用的是8.x版本,需要在pom文件中修改依赖 : 

2 . 修改redis的url,为自己虚拟机redis开放端口 : 

3 . 直接启动项目之后,访问http://localhost:8081/shop-type/list

4 . 将前端搭建好之后,访问8080,用手机模式打开 : 

2 . 基于Session实现短信验证登录

2 . 1 原理 : 

发送验证码:

用户在提交手机号后,会校验手机号是否合法,如果不合法,则要求用户重新输入手机号

如果手机号合法,后台此时生成对应的验证码,同时将验证码进行保存,然后再通过短信的方式将验证码发送给用户

短信验证码登录、注册:

用户将验证码和手机号进行输入,后台从session中拿到当前验证码,然后和用户输入的验证码进行校验,如果不一致,则无法通过校验,如果一致,则后台根据手机号查询用户,如果用户不存在,则为用户创建账号信息,保存到数据库,无论是否存在,都会将用户信息保存到session中,方便后续获得当前登录信息

校验登录状态:

用户在请求时候,会从cookie中携带者JsessionId到后台,后台通过JsessionId从session中拿到用户信息,如果没有session信息,则进行拦截,如果有session信息,则将用户信息保存到threadLocal中,并且放行 ;

2 . 2 发送短信验证码 : 

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1 . 检验啊手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            // 2 . 如果不符合 , 报错
            return Result.fail("手机号格式错误!") ;
        }
        // 3 . 符合 , 生成验证码
        String code = RandomUtil.randomNumbers(6) ; //生成长度为6位的随机验证码

        // 4 . 保存验证码到 session
        session.setAttribute("code",code);

        // 5 . 发送验证码
        log.debug("验证码方程成功,验证码 : {}",code);

        return Result.ok();
    }

关于如何校验,参考 : java实现手机号,密码,游邮箱 , 验证码的正则匹配工具类-CSDN博客

这里的发送验证码功能没有实现,只是做了个假的,日后有时间再完成 ;

启动项目,在前端点击发送验证码 : 

2 . 3 短信验证码登录和验证功能 :

    /**
     * 用户登录
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1 . 校验手机号
        String phone = loginForm.getPhone() ; // 获取手机号码
        if (RegexUtils.isPhoneInvalid(phone)){
            // 2 . 不符合 , 返回错误信息
            return Result.fail("手机号格式错误") ;
        }
        // 3 . 校验验证码
        Object cacheCode = session.getAttribute("code") ;
        String code = loginForm.getCode() ;
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            // 3 . 1 不一致,直接报错返回
            return Result.fail("验证码错误") ;
        }
        // 4 . 一致, 根据手机号查询用户
        User user = query().eq("phone",phone).one() ;

        // 5 . 判断用户是否存在
        if(user == null){
            // 6 . 为空,表示之前未创建 , 则创建
            user = createUserWithPhone(phone) ;
        }
        // 7 . 保存用户信息到session中
        session.setAttribute("user",user);

        return Result.ok() ;

    }

    private User createUserWithPhone(String phone) {
        // 1. 新建用户
        User user = new User() ;
        user.setPhone(phone) ;
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)) ;
        // 2 . 保存用户
        save(user) ;
        return user ;
    }

2 . 4 登录验证功能

先定义一个拦截器 : 

/**
 * 拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1 . 获取session
        HttpSession session = request.getSession()  ;
        // 2 .  获取session中的用户
        Object user = session.getAttribute("user") ;
        // 3 . 判断用户是否存在
        if(user==null){
            // 4 . 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false ;
        }
        // 5 . 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((User) user);
        // 6 . 放行
        return true ;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

这里因为之前登录的时候,在session中存了user信息,如果这里查不到,那就对其进行拦截,查到了,就放行 ;

然后让定义的拦截器生效 (定义一个配置类) : 

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "upload/**",
                        "/shop/**",
                        "/shop-type/**",
                        "voucher/**"
                );
    }
}

 其中设置了一些放行的端口(也就是不需要登录也能够访问得到的端口) ;

然后再实现一下"me"接口 : 

这里直接获取ThreadLocal中之前设置的user对象即可 ;

这里ThreadLocal来设置user和获取user定义成了一个工具类 : 

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;


public class UserHolder {
    private static final ThreadLocal<User> tl = new ThreadLocal<>();

    public static void saveUser(User user){
        tl.set(user);
    }

    public static User getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

到时候直接调用即可 ;

2 . 5 隐藏用户敏感信息

        用UserDto(只包括id,nickName,icon三个)来隐藏用户的敏感信息(如password,phone等),也可以减少内存的压力 ;

这里直接在存入session的时候,就转换为UserDTO : 

然后在LoginInterceptor中存入ThreadLocal的时候将user转换为UserDTo,

那么对应的UserHolder中也要改 : 

然后修改报错的地方,将User修改成UserDTO ;

然后重新登录测试 : 

2 . 6 session共享问题

用redis来解决session的内存不共享的问题 ;

2 . 7 Redis 代替 session

在登录发验证码的时候用手机号作为key,验证码作为value ;

在保存用户的时候 : 

用hash结构来保存用户信息 , 用一个随机的token作为key ;

在用session做登录校验的时候,tomcat会将session的id写到浏览器的cookie中,然后每一次的请求都会带着cookie,也就带着session_id , 然后就能够通过session_id找到session,然后找到用户 ;

在用redis代替token的时候,我们只能够手动的将token传给前端(客户端),然后客户端每一次请求都会携带token,然后我们可以基于token获取用户数据 ;

2 . 8 基于Redis实现短信登录

UserServiceImpl

发送短信验证码 : 

先注入StringRedisTemplate对象 : 

修改保存逻辑,将验证码保存到redis中以key= phone,value:code的形式,并且设置过期时间 : 

这里对于"login:code:"和2可以设置一个常量类保存起来代码更加规范 : 

完整代码 : 

    @Resource
    private StringRedisTemplate stringRedisTemplate ; // 注入

    /**
     * 发送手机验证码
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1 . 检验啊手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            // 2 . 如果不符合 , 报错
            return Result.fail("手机号格式错误!") ;
        }
        // 3 . 符合 , 生成验证码
        String code = RandomUtil.randomNumbers(6) ; //生成长度为6位的随机验证码

        // 4 . 保存验证码到 redis , 并设置两分钟的有效期(减少内存压力,防止一直点)
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);// 前面加一个login:code:标识,进行业务区分

        // session.setAttribute("code",code);

        // 5 . 发送验证码
        log.debug("验证码方程成功,验证码 : {}",code);

        return Result.ok();
    }
用户登录 : 
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1 . 校验手机号
        String phone = loginForm.getPhone() ; // 获取手机号码
        if (RegexUtils.isPhoneInvalid(phone)){
            // 2 . 不符合 , 返回错误信息
            return Result.fail("手机号格式错误") ;
        }
        // 3 . 校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone) ;// 本地code
        String code = loginForm.getCode(); // 前端传来的code
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            // 3 . 1 不一致,直接报错返回
            return Result.fail("验证码错误") ;
        }
        // 4 . 一致, 根据手机号查询用户
        User user = query().eq("phone",phone).one() ;

        // 5 . 判断用户是否存在
        if(user == null){
            // 6 . 为空,表示之前未创建 , 则创建
            user = createUserWithPhone(phone) ;
        }
        //  7 . 保存用户信息到redis中
        //  7 . 1 随机生成token , 作为登录令牌
        String token = UUID.randomUUID().toString(true);
        //  7 . 2 将User对象转换为hash存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class) ;
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
        // 7 . 3 存储
        String tokenKey = LOGIN_USER_KEY + token ;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        // 7 . 4 设置token有效期 (30分钟)
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);//这样是在登录那一刻的30分钟后就过期了,然后可以在拦截器哪里设置每一次访问就更新有效期

        //  8 . 返回token
        return Result.ok(token) ;

    }
  • 这里校验验证码,直接从redos中获取 ;
  • 保存用户到redis中 , 使用hash存储,用随机的token作为key(前面加一个标识前缀),用相应的user转换成map对象作为value ;
  • 最后还要设置token的有效期,这里只能够设置在登录之后有效期为30分钟,但是实际应该为每次访问的时候,都能够有30分钟的有效期,那么这个将在拦截器中设置 ;

LoginInterceptor : 

完整代码 : 

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import lombok.val;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate ;

    // 这里不能够使用Resource 和 AutoWired 等来进行注入,只能够使用构造函数来进行依赖注入
    // 因为 LoginInterceptor 是我们自己手动new出来的 , 不是由spring创建的 ;
    // 这里可以在MvcConfig中来注入 stringRedisTemplate 对象
    public LoginInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate ;
    }

    /**
     * 前置拦截器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1 . 获取请求头中的token
        String token = request.getHeader("authorization") ;
        if(StrUtil.isBlank(token)){
            // 4 . 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false ;
        }
        // 2 . 基于token获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token ;
        Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key) ;
        // 3 . 判断用户是否存在
        if(userMap.isEmpty()){
            // 4 . 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false ;
        }
        // 5 . 将查寻到的Hash数据转换为UserDTo对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6 . 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7 . 刷新token的有效期
        stringRedisTemplate.expire(key , 30, TimeUnit.MINUTES) ;
        // 8 . 放行
        return true ;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

这里主要进行修改的就是需要基于token从redis中获取数据 , 然后还要在每次拦截的时候,对token的有效期进行修改 ;

对于导入StringRedisTemplate方法参考 : 关于在拦截器中注入依赖对象-CSDN博客

报错 : 

运行起来之后,登录一下啊,能够发现报错 : 

能够发现,大概是 : 出现类型转换错误 :

详细参考 : java.lang.Long cannot be cast to class java.lang.String at redis.serializer.StringRedisSerializer报错-CSDN博客

测试 : 

能够看到请求头中携带了token,然后redis中也存入了响应的token ;

这样改造就完成了 ;

2 . 9 登录拦截器的优化 

        在上面方案中,他确实可以使用对应路径的拦截,同时刷新登录token令牌的存活时间,但是现在这个拦截器他只是拦截需要被拦截的路径,假设当前用户访问了一些不需要拦截的路径,那么这个拦截器就不会生效,所以此时令牌刷新的动作实际上就不会执行,所以这个方案他是存在问题的

首先加一个token刷新拦截器类 : 



package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import lombok.val;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 拦截器
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate ;

    // 这里不能够使用Resource 和 AutoWired 等来进行注入,只能够使用构造函数来进行依赖注入
    // 因为 LoginInterceptor 是我们自己手动new出来的 , 不是由spring创建的 ;
    // 这里可以在MvcConfig中来注入 stringRedisTemplate 对象
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate ;
    }

    /**
     * 前置拦截器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1 . 获取请求头中的token
        String token = request.getHeader("authorization") ;
        if(StrUtil.isBlank(token)){
            return true ;
        }
        // 2 . 基于token获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token ;
        Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key) ;
        // 3 . 判断用户是否存在
        if(userMap.isEmpty()){
            return true ;
        }
        // 5 . 将查寻到的Hash数据转换为UserDTo对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 6 . 存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7 . 刷新token的有效期
        stringRedisTemplate.expire(key , 30, TimeUnit.MINUTES) ;
        // 8 . 放行
        return true ;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

然后LoginInterceptor中就只需要执行拦截功能了 : 

 然后在MvcConfig中进行配置 : 

package com.hmdp.config;


import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate ;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "upload/**",
                        "/shop/**",
                        "/shop-type/**",
                        "voucher/**"
                ).order(1);
        // token刷新的拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

测试 : 在主界面刷新一下,然后看到redis中的时间重置了  : 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FressRTOS_day4:2024/4/4

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量的数值只有0和1。&#xff08;用于共享资源的访问&#xff09;&#xff1b;而计数型信号量的值一般是大于或者等于2&#xff08;用于生产者和消费者模型&#xff09; 2.使用计数型信号量…

JAVAEE——文件IO

文章目录 文件的概念什么是文件&#xff1f;树型结构组织 和 目录文件路径相对路径绝对路径 文件的分类文件的权限 文件读写IO API字符流操作API 警告字节流操作APIInputStreamOutputStream 文件的概念 什么是文件&#xff1f; 我们先来理解一下什么是文件&#xff0c;那么想…

【C++常用函数介绍】isalpha,isalnum、isdigit、islower、isupper 用法

首先 isalpha,isalnum、isdigit、islower、isupper 的使用方法都需要用到一个头文件 #include<ctype.h>其次 系统的介绍以上函数的用法 isalpha()用来判断一个字符是否为字母 isalnum&#xff08;&#xff09;用来判断一个字符是否为数字或者字母&#xff0c;也就是说…

四川尚熠电子商务有限公司靠谱吗?怎么样?

在当下数字化浪潮中&#xff0c;电子商务行业正以前所未有的速度蓬勃发展。四川尚熠电子商务有限公司&#xff0c;作为专注于抖音电商服务的企业&#xff0c;凭借其敏锐的市场洞察力和创新精神&#xff0c;正成为行业内的佼佼者&#xff0c;为众多品牌打开抖音电商市场的大门。…

Stable Diffusion扩散模型【详解】小白也能看懂!!

文章目录 1、Diffusion的整体过程2、加噪过程2.1 加噪的具体细节2.2 加噪过程的公式推导 3、去噪过程3.1 图像概率分布 4、损失函数5、 伪代码过程 此文涉及公式推导&#xff0c;需要参考这篇文章&#xff1a; Stable Diffusion扩散模型推导公式的基础知识 1、Diffusion的整体…

2006-2022年各省研发投入强度数据/研究与试验发展(RD)经费投入强度数据(无缺失)

2006-2022年各省研发投入强度数据/研究与试验发展(R&D)经费投入强度数据(无缺失) 1、时间:2006-2022年 2、范围&#xff1a;31省 3、来源&#xff1a;科技年鉴 4、指标&#xff1a;研发投入强度/研究与试验发展(R&D)经费投入强度 5、指标解释&#xff1a;研发投入…

算法沉淀 —— 动态规划(子序列问题(上))

算法沉淀 —— 动态规划&#xff08;子序列问题&#xff08;上&#xff09;&#xff09; 前言一、最长递增子序列二、摆动序列三、 最长递增子序列的个数四、最长数对链 前言 几乎所有的动态规划问题大致可分为以下5个步骤&#xff0c;后续所有问题分析都将基于此 1.、状态表示…

main函数的三个参数

main函数有三个参数 int main(int argc,char *argv[],char *env[]) 第一个参数argc argc:命令行参数的个数&#xff0c;当我们在命令行进行某些程序时需要一些参数 第二个参数argv argv:它是一个指针数组&#xff0c;里面存的是命令行参数的地址 第三个参数env env&…

如何减少与客户对需求理解的误差

大家好&#xff0c;我是不会魔法的兔子&#xff0c;持续分享项目管理中的风险及预防问题。 今天来说一说与客户对需求产生理解误差的问题&#xff0c;因为前些天看到小伙伴的吐槽&#xff0c;说明明是已经和客户就某个需求及解决方案说好了&#xff0c;结果东西做出来&#xf…

MySQL故障排查与优化

一、MySQL故障排查 1.1 故障现象与解决方法 1.1.1 故障1 1.1.2 故障2 1.1.3 故障3 1.1.4 故障4 1.1.5 故障5 1.1.6 故障6 1.1.7 故障7​ 1.1.8 故障8 1.1.9 MySQL 主从故障排查 二、MySQL优化 2.1 硬件方面 2.2 查询优化 一、MySQL故障排查 1.1 故障现象与解决方…

NVIDIA Jetson Xavier NX入门-镜像为jetpack5(3)——pytorch和torchvision安装

NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#xff08;3&#xff09;——pytorch和torchvision安装 镜像为jetpack5系列&#xff1a; NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#xff08;1&#xff09;——镜像烧写 NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#…

Stream API

Stream 是数据渠道&#xff0c;用于操作数据源&#xff08;集合、数组等&#xff09;所生成的元素序列。 Stream 和 Collection 集合的区别&#xff1a;Collection 是一种静态的内存数据结构&#xff0c; 讲的是数据&#xff0c;而 Stream 是有关计算的&#xff0c;讲的是计算。…

小林coding图解计算机网络|基础篇02|键入网址到网页显示,期间发生了什么?

小林coding网站通道&#xff1a;入口 本篇文章摘抄应付面试的重点内容&#xff0c;详细内容还请移步&#xff1a;小林coding网站通道 文章目录 孤单小弟——HTTP真实地址查询——DNS指南好帮手——协议栈可靠传输——TCP远程定位——IP两点传输——MAC出口——网卡送别者——交…

Linux是什么,该如何学习

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Linux的起源与发展 2、Linux在现代计算机领域…

Gatekeep AI:文本转视频教学工具,开启智能学习新纪元

在当今的数字时代,技术的进步不断改变着我们学习和理解知识的方式。 Gatekeep AI 就是这样一款令人兴奋的工具,它专注于将数学和物理问题通过文本提示转化为生动的视频。 特点与优势: 直观的可视化:将复杂的数学和物理概念以直观的视频形式呈现。快速生成:根据用户提供的…

SpringBoot + Vue + Nginx前后端分离项目本地部署(Win)

SpringBoot Vue Nginx前后端分离项目本地部署步骤 本地部署所需步骤 将后端打包好的jar文件和前端生成的静态资源文件放入同一目录启动Spring Boot应用配置Nginx并重启访问 http://your_domain 查看部署效果 前端Vue项目部署 将写好的vue代码的目录下运行 npm run build …

arm裸机(1)、点灯|按键

芯片是S3C2440 首先看原理图&#xff0c;led_1234分别对应引脚GPB 5678 设置引脚为输出 向寄存器相应位写入 #define GPBCON (*(volatile unsigned long *)0x56000010) //p5 6 7 8 void led_init(void) {GPBCON & ~(0x3 << 10);GPBCON | (0x1 <<…

网络编程(TCP、UDP)

文章目录 一、概念1.1 什么是网络编程1.2 网络编程中的基本知识 二、Socket套接字2.1 概念及分类2.2 TCP VS UDP2.3 通信模型2.4 接口方法UDP数据报套接字编程TCP流套接字编程 三、代码示例3.1 注意点3.2 回显服务器基于UDP基于TCP 一、概念 首先介绍了什么是网络编程&#xff…

【C语言】_文件内容操作:顺序读写

目录 常用函数 1. 字符输入、输出函数 2. 文本行输入、输出函数 3. 格式化输入、输出函数 4. 二进制输入、输出函数 常用函数 功能函数名适用于字符输入函数fgetc所有输入流字符输出函数fputc所有输出流文本行输入函数fgets所有输入流文本行输出函数fputs所有输出流格式化…

材料物理 笔记-4

原内容请参考哈尔滨工业大学何飞教授&#xff1a;https://www.bilibili.com/video/BV18b4y1Y7wd/?p12&spm_id_frompageDriver&vd_source61654d4a6e8d7941436149dd99026962 或《材料物理性能及其在材料研究中的应用》&#xff08;哈尔滨工业大学出版社&#xff09; 离…