redis黑马点评项目启动指南(含mac m1pro | windows11 wsl2 ubuntu环境配置 持续更新中~)

redis黑马点评项目学习笔记 mac m1pro windows 含项目配置教学

  • mac M1pro环境配置
  • windows11 wsl2 ubuntu 环境配置
  • 一.短信登录
    • 1. 1发送验证码
    • 1.2短信登录+注册
    • 1.3登录校验拦截器
    • 补缺Cookie Session Token
    • 1.4基于redis+token认证实现短信登陆
    • 1.5完善token认证的刷新机制
  • 2.商户查询缓存
    • 2.1添加商户缓存(到redis中)
    • 2.2添加商户类型缓存(到redis中)
    • 2.3(重点)redis中缓存更新策略分析(分四步)

mac M1pro环境配置

  1. 视频里用的是虚拟机,但我不想配虚拟机,太臃肿了,我选择docker这里我用的是OrbStack,这款软件使用的是rust编写,性能占用低(相比于dockerDesktop)
    brew insall orbstack或者去官网下载

  2. docker拉取redis 和 mysql镜像,注意常规镜像pull下来run后使用的是rosetta转译,性能会下降.
    因此我们拉取arm64v8/mysql和arm64v8/redis这两个镜像
    pull docker pull arm64v8/mysql
    pull docker pull arm64v8/redis
    在这里插入图片描述
    这里种类是Apple说明没有经过转译在这里插入图片描述

  3. 部署mysql和redis

    docker run -p 3306:3306 \--name mysql \-v mysql_data:/var/lib/mysql \-v mysql_conf:/etc/mysql/conf.d \--privileged=true \-e MYSQL_ROOT_PASSWORD=123456 \-d arm64v8/mysql
    
    docker run -p 6379:6379 \
    --name redis \
    -v redis_data:/data \
    -v redis_conf:/etc/redis/redis.conf \
    -d arm64v8/redis \
    redis-server /etc/redis/redis.conf
    

    使用docker ps查看是否成功
    在这里插入图片描述

  4. 进入mysql数据库中导入sql表并运行sql表语句

    # 1.拷贝SQL文件到mysql容器中(在容器外,自己的sql文件所在位置,mysql是容器名)
    docker cp yyy.sql mysql:/hmdp.sql# 2. 创建数据库(第一个mysql是容器名,第二个mysql是程序名称 也可以/bin/bash或者bash)
    docker exec -it mysql mysql  -uroot -p123456  
    mysql> create database hmdp;# 创建黑马点评数据库
    mysql> use hmdp; # 使用黑马点评数据库# 3.登陆控制台执行source 命令,执行sql文件
    mysql> source hmdp.sql
    
  5. 进入Datagrip软件查看一下redis 和 mysql(我用的这个可视化软件,它可以连接很多种数据库)
    在这里插入图片描述

  6. 项目后端

    # 1.clone
    git clone https://gitee.com/chiroua/black-horse-review.git
    # 2.切换分支, init分支就是项目最开始的代码
    git checkout init
    # 3.修改application.yaml中mysql和redis的ip,端口,密码等,这里我redis没有设置密码直接注释了.
    
    • 因为使用的是mysql8的版本
      还要将数据配置文件里driver-class-name: com.mysql.jdbc.Driver修改为如下driver-class-name: com.mysql.cj.jdbc.Driver;否则启动项目有报错
    • 修改idea默认的maven, idea自带一个叫已捆绑(Maven3)的maven,默认的所在路径为/.m2,settings和repo都在这个目录下,但是我没试过,我这里用的自己的maven仓库,记得修改一下maven仓库镜像地址,搜索关键词 maven 阿里
      在这里插入图片描述
      在这里插入图片描述
  7. 项目前端

  • brew install nginx
  • /opt/homebrew/Cellar/nginx/1.25.3/下的html夹替换成src/main/resources/nginx-1.18.0/下的html文件夹
  • /opt/homebrew/etc/nginx/nginx.conf替换成src/main/resources/nginx-1.18.0/conf/nginx.conf
    接下来重新加载nginx配置文件sudo nginx -s reload
  • 启动nginxsudo nginx
  • ps -ef | grep nginx查看nginx是否成功启动
  1. nginx成功启动后,我们启动后端,访问本地8080端口即可成功运行

windows11 wsl2 ubuntu 环境配置

  1. mysql

    # 安装mysql
    su root
    apt update
    sudo apt-get install mysql-server
    sudo service mysql start#一般就直接启动了不需要这一步
    mysql
    ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';# 修改root用户密码
    exit
    sudo mysql_secure_installation # 使用脚本删除匿名用户和匿名用户访问的数据库## 接下来 执行sql文件,构建数据库
    cp /mnt/c/.....hmdp.sql /tmp/ #每个人不一样,可以参考我复制到/tmp目录下
    mysql -u root -p
    CREATE DATABASE hmdp;
    use hmdp;
    source /tmp/hmdp.sql
    

    更多操作查看此博客

  2. redis

    # 我直接安装的 没设置密码
    su root
    apt update
    sudo apt-get install redis-server
    # 执行redis-cli查看redis正常启动即可
    
  3. datagrip连接redis和mysql, 其中redis指定不需要用户和密码即可

  4. git clone git@gitee.com:chiroua/black-horse-review.git
    # 修改maven仓库的settings 将源设置成aliyun,这里我直接用idea自带的maven仓库了
    # 修改applicati.yml配置文件中mysql 和 redis相关配置
    

    因为使用的是mysql8的版本
    还要将数据配置文件里driver-class-name: com.mysql.jdbc.Driver修改为如下driver-class-name: com.mysql.cj.jdbc.Driver;否则启动项目有报错

  5. 前端也有一处问题 niginx目录下打开终端start nginx.exe后访问8080无效,
    查看nginx目录下的logs/error.log发现2024/01/04 21:58:41 [emerg] 37092#35300: CreateDirectory() "C:\Users\gx\Desktop\hmdp\black-horse-review\src\main\resources\nginx-1.18.0/temp/client_body_temp" failed (3: The system cannot find the path specified)
    我们手动在logs的同级目录下创建一个temp/client_body_temp即可

一.短信登录

1. 1发送验证码

  • 业务思路: 基本的校验->验证码和手机号保存在session中(不明白session token cookie去学),利用RandomUtil工具类生成验证码即可;session中存储的手机号和验证码是为了登录时的校验功能
@Overridepublic Result sendCode(String phone, HttpSession session) {//获得用户手机号//1.校验手机号正确性if (RegexUtils.isPhoneInvalid(phone))return Result.fail("手机号格式不正确");session.setAttribute("phone", phone);//2.生成验证码String code = RandomUtil.randomNumbers(6);//3.保存验证码session.setAttribute("code", code);//4.发送验证码log.debug("发送短信验证码成功, 验证码:{}", code);//5.返回okreturn Result.ok();}

1.2短信登录+注册

  • 业务思路:controller 中 return UserService接口中的方法, 具体实现在实现类UserServiceImpl中去重写即可;基本的参数校验->从session中取出验证码和手机号->判断手机号前后是否一致,判断验证码是否正确,判断用户是否存在,不存在则注册一个保存到数据库中
  • 说在前面: 本项目使用了mybatis-plus, 直接的好处是可以直接用save(), query等api, 原因是因为
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService
    我们观察发现 UserServiceImpl类继承了 ServiceImpl<T,T>, 同时指定了两个泛型UserMapper,和User,我们去entity下的user类可以发现,其中指明了该实体类对应的表名,这就是二者之间的联系.在这里插入图片描述
  • 代码优化:这里我优化了一下, 应当先检查当前手机号和session中存储的手机号是否一致
  • 注意代码命名规则,:比如session中的code命名为cacheCode,;类名:驼峰命名法+ 首字母大写, 方法名驼峰命名法+首字母小写
  • api设计规则:设计的类名称为IShopTypeService,对应的路径名称为shop-type
  • 写代码流程:先把思路捋清楚,比如1.2.3.4.每一步干什么,然后再写代码
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.校验手机号格式是否正确String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)) return Result.fail("手机号格式不正确!");//这里不知道为啥,不用return true代表格式不正确//2.校验手机号是否和session里面的一致String cachePhone = (String) session.getAttribute("phone");if (!cachePhone.equals(phone)) return Result.fail("前后手机号不匹配!");//3.校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if (cacheCode == null || !cacheCode.toString().equals(code)) return Result.fail("验证码错误!");//4.判断用户是否存在User user = query().eq("phone", phone).one();//5.不存在的话直接创建一个用户if (user == null) user = createUserWithPhone(phone);//6.保存用户信息到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;}

1.3登录校验拦截器

  • 业务思路:
    • 设计拦截器的原因: 点评的不少业务都需要验证当前是否是登录状态,比如我们观察前端info.html(个人信息界面)的代码发现会发送如下请求在这里插入图片描述
      其中有一个Cookie, 如果每个业务都要从http request的session中取的话会很麻烦,因此我们设置一个登录拦截器LoginInterceptor类, 所有的业务都会先经过这个类最后到达controller, 我们在这个类中将当前session中的user属性存储到本地线程中,要用的时候直接从本地线程UserHolder类(封装了TreadLocal类对象)中取就行.
    • 防止用户隐私泄露: 我们session中一开始存的是完整的user对象(包含了user的密码等个人隐私), 我们封装一个UserDto类, 将user中的信息拷贝到userDto中, session中存userDto就行, 后续的UserHolder等cache类从session中拿到的数据也就是userDto了. 但是我们createUser方法往数据库中存储的时候还是存储完整的user对象哦
    • 实现WebMvcConfigurer类中的addInterceptor方法, 并设置哪些请求路径不需要被拦截
  • 除了拦截器外还可以设计过滤器
  • 缺: 什么是TreadLocal内存泄露,为什么afterCompletion后要removeUser(); 什么是session cookie token
  • mac idea 实现方法快捷键 ^ + o
 @Overridepublic Result me() {UserDTO user = UserHolder.getUser();return Result.ok(user);}
@Data
public class UserDTO {private Long id;private String nickName;private String icon;
}
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取session中的用户HttpSession session = request.getSession();Object user = session.getAttribute("user");//2.用户是否存在,不存在返回false,并设置状态码if (user == null){response.setStatus(401);return false;}//3.存在就将当前用户存储在ThreadLocal中UserHolder.saveUser((UserDTO) user);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户,防止内存泄漏UserHolder.removeUser();}
}
@Configuration
public class MyConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(// 匿名对象 stream coding"/user/code","/voucher/**","/blog/hot","/shop/**","/shop-type/**","/upload/**","/user/login");}
}

补缺Cookie Session Token

因为Http是一种无状态的协议,为了记住这种状态, 引入了 session cookie token

  • Cookie: 存储在前端, 数据的一种载体, 后端可以将数据包装在Cookie中发送给客户端端, 比如将session放在cookie中送到客户端, cookie跟随HTTP的每个请求发送出去
  • Session: 存储在后端
  • Token: 在很多地方都会用到, 只是一个通用的名词;诞生在服务器这端, 但保存在浏览器这边, 由客户端主导一切, 可以存放在Cookie或者Storage中, jwt即json web token是一种特殊的token,或者说token的一种
  • 总结: cookie是载体,而token和session则是靠它实现的验证机制,本质就是cookie上携带的字符串; session和token是两种认证机制。cookie和localstorage等是存储session id或token的载体

1.4基于redis+token认证实现短信登陆

  • 业务思路: 这里采用token认证机制, 不再使用session了, 因为如果是tomcat服务器集群,session存储在不同的tomcat服务器中, 假如小明的session存储在A服务器中, 第二次小明发送请求的时候经过负载均衡请求到达B服务器, 那么B服务器没有小明的session,小明又要重新登录; 但是如果让多个服务器之间数据同步的话会造成数据冗余,同时同步数据也需要一定的时间,综上我们选择redis来存储数据
    • 用redis存储code, phone作为其key
    • 用redis存储user信息, token作为其keytoken的本质是一种验证机制, 这里token作为当前用户个人信息的key!!
    • springboot内置的tomcat会帮我们维护session,这里我们自己维护redis的时候注意数据的丢失, 因为不可能让数据一直存储在redis中
    • 具体实现业务代码的时候注意自己new的对象spring不会帮我们管理的
  • 缺: 什么样的对象可以让spring帮我们管理?
 @Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {//获得用户手机号//1.校验手机号正确性if (RegexUtils.isPhoneInvalid(phone))return Result.fail("手机号格式不正确");//session.setAttribute("phone", phone); 使用redis后我还没想好它的key是啥//2.生成验证码String code = RandomUtil.randomNumbers(6);//3.保存验证码到redis中并设置缓存时间stringRedisTemplate.opsForValue().set(LOGIN_USER_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//stream流//4.发送验证码log.debug("发送短信验证码成功, 验证码:{}", code);//5.返回okreturn Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.校验手机号格式是否正确String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)) return Result.fail("手机号格式不正确!");//这里不知道为啥,不用return true代表格式不正确
//        //2.校验手机号是否和session里面的一致
//        String cachePhone = (String) session.getAttribute("phone");
//        if (!cachePhone.equals(phone)) return Result.fail("前后手机号不匹配!");//3.校验验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_USER_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) return Result.fail("验证码错误!");//4.判断用户是否存在User user = query().eq("phone", phone).one();//5.不存在的话直接创建一个用户if (user == null) user = createUserWithPhone(phone);//6.保存用户信息到redis中, token作为key并设置缓存时间String token = UUID.randomUUID().toString(true);String tokenKey = LOGIN_USER_KEY +  token;Map<String, Object> userMap = BeanUtil.beanToMap(user, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);//设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);//7.返回tokenreturn Result.ok(token);}
public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;//用来接住外部传过来的StringRedisTemplatepublic LoginInterceptor(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获得token, 判断token是否为空String token = request.getHeader("authorization");if (StrUtil.isBlank(token)) return false;//2.通过token获取redis中的用户信息,并判断同户是否存在,不存在返回false,并设置状态码String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);if (userMap.isEmpty()){//及时为空也会被包装成一个对象,因此这里不能判断null而是isEmptyresponse.setStatus(401);return false;}//3.将map变成userDtoUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//4.存在就将当前用户存储在ThreadLocal中UserHolder.saveUser(userDTO);//5.每拦截一次, redis中存储的用户信息刷新一次stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户,防止内存泄漏UserHolder.removeUser();}
}
@Configuration
public class MyConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;//该类中有一个@Configuration注解说明该类可以由spring来帮我们管理对象@Overridepublic void addInterceptors(InterceptorRegistry registry) {//再该类中让spring帮我们管理对象然后传给LoginInterceptorregistry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(// 匿名对象 stream coding"/user/code","/voucher/**","/blog/hot","/shop/**","/shop-type/**","/upload/**","/user/login");}
}

1.5完善token认证的刷新机制

  • 业务思路: 因为之前的一层拦截器只能拦截部分请求, 假如用户一直在浏览index页面30min,那么再访问需要user信息的页面的时候就会需要重新登录, 因此这里再多设置一层拦截器拦截所有请求
    • 第一层拦截器: 拦截所有请求 + 将当前用户信息存储到ThreadLocal中;判断是否有用户的任务交给下一层,本层的任务是存储user到当前thread+刷新token(不判断token是否为空, 若无token代表无user信息, 无user信息的情况给第二层拦截器处理)
    • 第二层拦截器: 拦截部分需要user信息的页面的请求, 若从ThreadLocal中取出来的数据为空则直接return false 即可
    • 注意注册拦截器的时候拦截器有优先级顺序, 默认情况下都是0, 谁先注册谁先执行, 或者我们手动更改拦截器的优先级(优先级越小越先执行)
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;//用来接住外部传过来的StringRedisTemplatepublic RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获得token, 判断token是否为空String token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;//均return true, 判断是否有用户的任务交给下一层,本层的任务是存储user到当前thread+刷新token}//2.通过token获取redis中的用户信息,并判断同户是否存在,不存在返回false,并设置状态码String tokenKey = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);if (userMap.isEmpty()){//及时为空也会被包装成一个对象,因此这里不能判断null而是isEmptyreturn true;}//3.将map变成userDtoUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//4.存在就将当前用户存储在ThreadLocal中UserHolder.saveUser(userDTO);//5.每拦截一次, redis中存储的用户信息刷新一次stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);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.从UserHolder获得用户UserDTO userDto = UserHolder.getUser();//2.判断是否有user用户if (userDto == null) {response.setStatus(401);return false;}return true;}//不用重写afterCompletion方法, 第一层拦截器负责重写拦截器执行顺序:preHandle: 拦截器1 -> 拦截器2afterCompletion: 拦截器2 - > 拦截器1
}
@Configuration
public class MyConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;//该类中有一个@Configuration注解说明该类可以由spring来帮我们管理对象@Overridepublic void addInterceptors(InterceptorRegistry registry) {//再该类中让spring帮我们管理对象然后传给LoginInterceptorregistry.addInterceptor(new LoginInterceptor()).excludePathPatterns(// 匿名对象 stream coding"/user/code","/voucher/**","/blog/hot","/shop/**","/shop-type/**","/upload/**","/user/login").order(1);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);}
}

2.商户查询缓存

从本节开始我会说明这个业务的原因和业务的思路是如何的

2.1添加商户缓存(到redis中)

  • 业务原因
  • 业务思路
    查看网络请求时间验证是否快了很多

2.2添加商户类型缓存(到redis中)

2.3(重点)redis中缓存更新策略分析(分四步)

  • 业务原因: 如果数据库中的数据更新了, redis中的缓存如何更新?更新选择什么样的更新策略才能保证效率, 数据的一致性等问题?
  • 业务思路:
    1. 首先我们看下缓存有哪些更细策略,对应的维护方法是如何的
      在这里插入图片描述
      在这里插入图片描述
    2. 上面我们知道了内存淘汰机制和超时剔除机制是如何实现的, 接下来重点介绍主动更新,主动更新分以下三种, 最终我们考虑到数据的可靠性,我们还是选择第一种.
      在这里插入图片描述
    3. 上面我们已经考虑完选择Cache Aside这种数据库和缓存同时更新的策略,接下来思考细节问题:
      1. 更新缓存采用什么方式?结论:删除缓存的方式
      1. 如何保证原子性?结论:用事务机制
      1. (重点)先操作数据库还是缓存? 这两种方案各自会有何问题(考虑微观上操作系统中线程是抢占式的,及时代码中写了先xx在yy,但我们更新xx后再更新yy时, xx可能还没更新完或者一系列问题)在这里插入图片描述
    1. 接下来我们探讨上面遗留下来的问题先操作数据库还是缓存?我们观察得知,其实发生下面这两种异常情况的主要原因是,删除缓存 更新数据库 查询缓存未命中,查询数据库 写入缓存这四步因为线程执行顺序问题导致的一系列的后果; 观察下图的时候主要观察删除缓存 更新数据库这两步
      先删除缓存再操作数据库:在这里插入图片描述

在这里插入图片描述
先操作数据库再删除缓存在这里插入图片描述
在这里插入图片描述
方案二先操作数据库再删除缓存这种方案出现异常情况的概率比较低, 因为2.更新数据库这一步操作时间一般比较长, 想让4.写入缓存2.更新数据库后面发生的概率比较低;
要么查询缓存写入缓存都在更新数据库前面,那么查询缓存和写入缓存的都是旧数据之后靠淘汰机制兜底;
要么就都在更新数据库后面, 这样你未命中缓存后查数据库也是查的新数据.

  • 总结就一句话:先干时间长的, 再干时间短的
    这种同步问题不要绕弯子, 太多细节会把自己弄晕,身为初学者, 直接先更新数据库再删除缓存就完事了;
  • 因为数据库的IO速度慢,时间长,如果先更新数据库那么在当前线程执行更新数据库操作删除缓存这期间很容易发生幺蛾子, 比如其他线程的查询操作来了,这样查询的和缓存的都是旧数据;
  • 但如果先删除缓存, 再更新数据库就会好很多,因为缓存的操作很快的, 你如果再缓存到更新数据库期间发生幺蛾子(那么大不了查询到的是旧数据,但起码缓存中存储的是新数据呀)
    在这里插入图片描述

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

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

相关文章

Python浪漫520表白代码

系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏飘字表白代码https://want595.blog.csdn.net/article/details/1349149703无限弹窗表白代码https://want595.blog.csdn.net/article/details/1297945184跳…

springboot基础

一 springboot 1 spring 的缺点 在spring boot出现以前&#xff0c;使用spring框架的程序员是这样配置web应用环境的&#xff0c;需要大量的xml配置。下图展示了在xml配置的时代和SpringBoot的配置量的差别。 随着web项目集成软件的不断增多&#xff0c;xml配置也不断的增多&…

编程笔记 html5cssjs 026 HTML输入类型(2/2)

编程笔记 html5&css&js 026 HTML输入类型&#xff08;2/2&#xff09; 输入类型&#xff1a;date输入类型&#xff1a;color输入类型&#xff1a;range输入类型&#xff1a;month输入类型&#xff1a;week输入类型&#xff1a;time输入类型&#xff1a;datetime输入类型…

java数据结构与算法刷题-----LeetCode63. 不同路径 II

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 很多人觉得动态规划很难&#xff0c;但它就是固定套路而已。其实动态规划只…

第3章-指标体系与可视化-3.3-指标体系

目录 为什么要构建指标体系 什么是指标体系 如何构建指标体系 指标库

C#编程-实现继承

C#允许您通过扩展现有类的功能以创建新类来实现继承。 从基类创建派生类 使用以下语法在C#中创建派生类: class <derived_class>:<base_class>{...}确定继承的层次结构 要确定继承层次结构,必须检查派生类与基类之间的关系种类。确保派生类是一种基类。 请考虑以…

Linux——系统安全及应用

一、基本安全措施 1、系统账号清理 常见的非登录用户账号包括bin、daemon、 adm、lp、mail等。为了确保系统安全&#xff0c;这些用户账号的登录Shell通常是/ sbin/nologin&#xff0c;表示禁止终端登录&#xff0c;应确保不被人为改动。 //将非登陆用户的Shell设为/sbin/nolo…

【Spring Cloud】组件概念详解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Cloud》。&#x1f3af;&#x1f3af; &am…

2023 hnust 湖南科技大学 大四上 商务智能 课程 期末考试 复习资料

前言 《听了课就能及格》由于老师发的复习PPT内容过多&#xff08;近两万字&#xff09;&#xff0c;因此在此大幅删减由于老师透露太少&#xff0c;删减全凭主观意志&#xff0c;请谨慎参考&#xff01;&#xff01;&#xff01;猜测逻辑 过于细碎概念的不考&#xff08;不算…

【Tomcat】在一台计算机上运行两个Tomcat服务

首先把Tomcat整个文件复制一份放在其他文件夹路径中 1.修改环境变量 添加环境变量在系统变量里面 “CATALINA_HOME” 指向一个Tomcat文件夹路径 “CATALINA_HOME1” 指另一个Tomcat文件夹路径 2.修改startup里面的环境变量&#xff0c;全部修改 分别修改两个apache-tomcat…

【STM32】RTC实时时钟

1 unix时间戳 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同&#xff0c;不同时区…

Linux第14步_安装FTP服务器

安装“vim编辑器”后&#xff0c;我们紧接着“安装FTP服务器”。 1、在安装前&#xff0c;要检查虚拟机可以上网&#xff0c;否则可能会导致安装失败。 2、在虚拟机界面右击鼠标&#xff0c;弹出下面的对话框 3、点击“打开终端(E)”&#xff0c;得到下面的界面 &#xff1a;…

基于SpringBoot的高校毕业生离校管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的高校毕业生离校管理系统…

SQL Server 权限管理

CSDN 成就一亿技术人&#xff01; 2024年 第一篇 难度指数&#xff1a;* * CSDN 成就一亿技术人&#xff01; 目录 1. 权限管理 什么是权限管理&#xff1f; SQL server的安全机制 服务器级角色 数据库级角色 对象级角色 2. 创建用户 赋予权限 最重要的一步骤 1. 权限…

oracle和mysql怎么传输数据

传输Oracle和MySQL之间的数据可以使用多种方法&#xff0c;以下是其中两种常用的方法&#xff1a; 使用ETL工具&#xff1a;ETL工具是一种用于数据抽取、转换和加载的工具&#xff0c;可以将数据从一个数据库传输到另一个数据库。常用的ETL工具有Apache NiFi、Talend、Pentaho…

使用arm-linux-gcc出现Syntax error: word unexpected (expecting “)“)

使用arm-linux-gcc出现Syntax error: word unexpected (expecting ")") 出现这样的报错该怎么解决&#xff1f; arm-linux-gcc 1.c -o 1 /usr/local/arm/usr/local/arm/5.4.0/usr/bin/../lib/gcc/arm-none-linux-gnueabi/5.4.0/../../../../arm-none-linux-gnueabi/b…

MySQL与Oracle数据库在网络安全等级方面用到的命令

MySQL数据库命令集 查看数据库版本 SELECT VERSION(); 空口令查询 SELECT user,host,account_locked FROM mysql.user WHERE user ; SELECT * FROM mysql.user; 查询 用户的密码加密情况 SELECT HOST,USER,PLUGIN FROM mysql.user; 查询是否有空用户 SELECT host,user,plug…

henauOJ 1102: 词组缩写

题目描述 定义&#xff1a;一个词组中每个单词的首字母的大写组合称为该词组的缩写。 比如&#xff0c;C语言里常用的EOF就是end of file的缩写。 输入 输入的第一行是一个整数T&#xff0c;表示一共有T组测试数据&#xff1b; 接下来有T行&#xff0c;每组测试数据占一行&a…

深入理解神经网络训练与反向传播

目录 前言1 损失函数1.1 交叉熵&#xff08;Cross Entropy&#xff09;&#xff1a;1.2 均方差&#xff08;Mean Squared Error&#xff09;&#xff1a; 2 梯度下降与学习率2.1 梯度下降2.2 学习率 3 正向传播与反向传播3.1 正向传播3.2 反向传播 4 链式法则和计算图4.1 链式法…

2023 hnust 湖南科技大学 大四上 计算机图形图像技术 课程 期末考试 复习资料

计算机图形图像技术复习资料 前言 改编自&#xff1a;https://blog.csdn.net/Liu_Xin233/article/details/135232531★重点&#xff0c;※补充github 考试题型 简述题&#xff08;10分4题&#xff0c;共40分&#xff09; 第1章的基本内容三维观察流水线中的基本概念与理解三…