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

后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权

目录

后端注册拦截器

实现对 WebMvcConfigurer 接口的类实现

静态变量

方法重写 注册 Spring Framework拦截器

Sa-Token中SaServletFilter拦截器

思考 为什么使用两个拦截器

1. Spring Framework 拦截器

2. SaServletFilter

为什么要注册两个拦截器?

总结

完整代码

后端注册权限验证接口扩展

实现 Satoken 的 StpInterface 接口

获取用户和权限码

在 Dao 层实现

第一张表是 菜单表

第二张表是 用户表

多表联查 SQL 语句

遍历获取权限 getPermissionList 方法

获取角色 getRoleList 方法

主要逻辑

权限与角色的关系

数据库查询

SaSession 和缓存

完整代码

后端自定义侦听器

doLogin — 用户登录时触发

总结

完整代码


后端注册拦截器

实现对 WebMvcConfigurer 接口的类实现

静态变量

一个是不需要鉴权的网址

一个是超时过期时间

方法重写 注册 Spring Framework拦截器

这段代码的目的是注册多个拦截器,在 Spring 应用中统一处理:

  1. 分页:处理分页相关的逻辑。
  2. 限流:通过 Redis 实现请求频率控制,防止滥用。
  3. 权限鉴权:使用 Sa-Token 框架进行请求的权限验证。

所有这些拦截器会在请求到达控制器之前执行,确保全局的功能逻辑一致性(如分页、限流和权限检查)。

@Override
public void addInterceptors(InterceptorRegistry registry) {// 注册分页拦截器registry.addInterceptor(new PageableInterceptor());// 注册Redis限流器registry.addInterceptor(accessLimitInterceptor);// 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}

Sa-TokenSaServletFilter拦截器

根据文档里面的写法

    @Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter()// 拦截路径.addInclude("/**")// 放开路径.addExclude(EXCLUDE_PATH_PATTERNS)// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {SaHolder.getResponse()// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", "*")// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "*")// 有效时间.setHeader("Access-Control-Max-Age", "3600")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "*");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理")).back();})// 认证函数: 每次请求执行.setAuth(obj -> {// 检查是否登录SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());// 刷新token有效期if (StpUtil.getTokenTimeout() < timeout) {StpUtil.renewTimeout(1800);}// 输出 API 请求日志,方便调试代码SaManager.getLog().debug("请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());})//  异常处理函数:每次认证函数发生异常时执行此函数.setError(e -> {// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if (e instanceof NotLoginException) {// todo 确实是这边有问题e.printStackTrace();return JSONUtil.toJsonStr(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));}// TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端e.printStackTrace();return SaResult.error(e.getMessage());});}

思考 为什么使用两个拦截器

1. Spring Framework 拦截器

Spring 框架中的拦截器通常是通过实现 HandlerInterceptor 接口,或通过继承 WebMvcConfigurer 类中的 addInterceptors 方法来注册的。这类拦截器一般用于以下目的:

  • 日志记录:记录请求的日志。
  • 权限检查:用于访问控制,判断用户是否有权限访问某些资源。
  • 性能监控:统计接口响应时间等。
  • 请求处理:在请求进入控制器前对请求进行预处理,或在请求完成后进行后处理。

Spring 的拦截器是基于 HandlerInterceptor 接口的,它是为 Spring MVC 控制器定制的,具有以下生命周期:

  1. preHandle:在请求到达控制器之前执行。
  2. postHandle:在控制器方法执行后,渲染视图之前执行。
  3. afterCompletion:视图渲染完毕后执行,通常用于清理资源。

在这段代码中,Spring 的拦截器被注册为:

  • 分页拦截器:用于处理分页参数(如 pagesize),确保分页逻辑的一致性。
  • 限流拦截器:用于限制 API 请求的频率,防止过于频繁的请求对服务器造成压力。
  • 权限拦截器:用于进行权限检查,确保用户请求的资源需要相应的权限。

2. SaServletFilter

SaServletFilter 是 Sa-Token 框架提供的一个过滤器,用于处理认证和权限管理。它作为一个 Servlet Filter 被引入到 Spring Web 应用中,并在请求进入控制器之前执行。SaServletFilter 的职责通常包括:

  • 用户认证:检查请求是否携带有效的 token,判断用户是否登录。
  • 跨域处理:配置跨域请求的响应头,确保前端能够正常访问接口。
  • 权限控制:根据不同的 URL 路径,检查用户是否具有访问权限。
  • 异常处理:在认证或权限检查失败时,返回统一的错误信息。

SaServletFilter 是一个全局过滤器,会拦截所有请求,处理权限相关的逻辑。它的作用是在用户访问接口时进行认证、权限检查、跨域处理等。

为什么要注册两个拦截器?

  1. 职责分离
    • Spring 拦截器 主要用于通用功能,如分页、限流、日志等,这些是应用中与业务逻辑和请求处理相关的通用功能。
    • SaServletFilter 主要负责安全相关的功能,如认证和权限检查。它是 Sa-Token 提供的专用过滤器,能够帮助应用实现基于 token 的权限控制。
  1. 功能互补
    • SaServletFilter 是为了处理与用户认证、权限相关的安全需求,而 Spring 拦截器通常用于通用功能(如分页、限流)。这两者的作用并不冲突,反而可以互补,Spring 拦截器可以集中处理一些公共逻辑,SaServletFilter 则专注于用户认证和权限控制。
  1. 实现细粒度控制
    • 在复杂的应用中,你可能需要对某些路径进行分页和限流控制,但对于其他路径,你需要确保严格的权限检查。SaServletFilter 提供了灵活的权限认证机制,而 Spring 的拦截器可以细分不同的逻辑(如分页、限流等),实现更精细的控制。
  1. 统一认证与权限管理
    • SaServletFilter 通过拦截所有请求并统一处理认证、跨域、权限等,确保每个请求都能经过安全检查。Spring 拦截器负责处理请求中的其他逻辑(如日志记录、分页等),这样可以将应用中的各个功能模块进行解耦,并且保证权限检查与认证操作的统一性。

总结

  • Spring Framework 拦截器:主要负责处理与请求相关的公共逻辑,如分页、限流、日志记录等。
  • SaServletFilter:主要负责安全相关的功能,执行认证、权限控制、跨域处理等操作。

通过同时注册这两个拦截器,应用能够既保持业务逻辑的清晰和分离,又能确保安全性(认证与权限管理)得到充分保障。两者各司其职,共同为应用提供完整的功能支持。

完整代码

package com.ican.satoken;import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.json.JSONUtil;
import com.ican.interceptor.AccessLimitInterceptor;
import com.ican.interceptor.PageableInterceptor;
import com.ican.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import static com.ican.enums.StatusCodeEnum.UNAUTHORIZED;/*** SaToken配置** @author Dduo* @date 2024/11/28 22:12**/
@Slf4j
@Component
public class SaTokenConfig implements WebMvcConfigurer {@Autowiredprivate AccessLimitInterceptor accessLimitInterceptor;private final String[] EXCLUDE_PATH_PATTERNS = {"/swagger-resources","/webjars/**","/v2/api-docs","/doc.html","/favicon.ico","/login","/oauth/*",};private final long timeout = 600;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册分页拦截器registry.addInterceptor(new PageableInterceptor());// 注册Redis限流器registry.addInterceptor(accessLimitInterceptor);// 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");}@Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter()// 拦截路径.addInclude("/**")// 放开路径.addExclude(EXCLUDE_PATH_PATTERNS)// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {SaHolder.getResponse()// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", "*")// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "*")// 有效时间.setHeader("Access-Control-Max-Age", "3600")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "*");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理")).back();})// 认证函数: 每次请求执行.setAuth(obj -> {// 检查是否登录SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());// 刷新token有效期if (StpUtil.getTokenTimeout() < timeout) {StpUtil.renewTimeout(1800);}// 输出 API 请求日志,方便调试代码SaManager.getLog().debug("请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());})//  异常处理函数:每次认证函数发生异常时执行此函数.setError(e -> {// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if (e instanceof NotLoginException) {// todo 确实是这边有问题e.printStackTrace();return JSONUtil.toJsonStr(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));}// TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端e.printStackTrace();return SaResult.error(e.getMessage());});}}

后端注册权限验证接口扩展

实现 Satoken 的 StpInterface 接口

获取用户和权限码

根据文档里面的内容

每个用户 id 都对应一系列的权限码

每个账号都会拥有一组权限码集合,框架来校验这个集合中是否包含指定的权限码。

我们将权限码存到数据库里面

在 Dao 层实现

第一张表是 菜单表

第二张表是 用户表

多表联查 SQL 语句

<select id="selectPermissionByRoleId" resultType="java.lang.String">SELECT DISTINCT m.permsFROM t_menu mINNER JOIN t_role_menu rm ON m.id = rm.menu_idWHERE rm.role_id = #{roleId}AND m.is_disable = 0
</select>

遍历获取权限 getPermissionList 方法

/*** 返回一个账号所拥有的权限码集合** @param loginId   登录用户id* @param loginType 登录账号类型* @return 权限集合*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {// 声明权限码集合List<String> permissionList = new ArrayList<>();// 遍历角色列表,查询拥有的权限码for (String roleId : getRoleList(loginId, loginType)) {SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);List<String> list = roleSession.get("Permission_List", () -> menuMapper.selectPermissionByRoleId(roleId));permissionList.addAll(list);}// 返回权限码集合return permissionList;
}
  • 角色与权限的查询:通过 getRoleList 获取用户角色,再通过 getPermissionList 获取每个角色的权限。
  • 会话缓存:使用 SaSession 缓存角色和权限信息,减少对数据库的查询。
  • 自定义权限与角色管理:通过扩展 StpInterface 接口,灵活地实现了用户角色和权限的管理,适应应用中的业务需求。

获取角色 getRoleList 方法

@Override
public List<String> getRoleList(Object loginId, String loginType) {SaSession session = StpUtil.getSessionByLoginId(loginId);return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));
}
  • 功能:返回一个账号(loginId)所拥有的所有角色。
  • 实现步骤
    1. 通过 StpUtil.getSessionByLoginId(loginId) 获取当前登录用户的会话。
    2. 从会话中获取角色列表。如果会话中没有角色信息,则通过 roleMapper.selectRoleListByUserId(loginId) 查询数据库获取。
    3. 返回角色列表。

主要逻辑

  • getRoleList 方法用于获取用户的角色列表。每个用户可以有多个角色,角色是权限的载体。
  • getPermissionList 方法根据用户的角色列表,查询每个角色对应的权限。权限是基于角色的,角色是用户的身份标识。权限是用户访问特定资源的授权标识。

权限与角色的关系

  • 在这个实现中,用户是通过角色来管理权限的。每个用户有一组角色,而每个角色又有一组权限。这种关系是典型的 角色权限控制(RBAC) 模式。
  • getRoleList 获取用户的角色列表,而 getPermissionList 则是基于角色来获取相应的权限。

数据库查询

  • menuMapper.selectPermissionByRoleId(roleId):根据角色 ID 查询该角色所拥有的权限。权限通常与菜单或 API 请求相关。
  • roleMapper.selectRoleListByUserId(loginId):根据用户 ID 查询该用户所拥有的角色。

SaSession 和缓存

  • SaSession 在这个实现中被用于缓存角色和权限信息。这样,避免每次请求都进行数据库查询。使用会话可以提高性能,避免重复查询数据库。

完整代码

package com.ican.satoken;import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.ican.mapper.MenuMapper;
import com.ican.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 自定义权限验证接口扩展** @author Dduo*/
@Component
public class StpInterfaceImpl implements StpInterface {@Autowiredprivate MenuMapper menuMapper;@Autowiredprivate RoleMapper roleMapper;/*** 返回一个账号所拥有的权限码集合** @param loginId   登录用户id* @param loginType 登录账号类型* @return 权限集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 声明权限码集合List<String> permissionList = new ArrayList<>();// 遍历角色列表,查询拥有的权限码for (String roleId : getRoleList(loginId, loginType)) {SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);List<String> list = roleSession.get("Permission_List", () -> menuMapper.selectPermissionByRoleId(roleId));permissionList.addAll(list);}// 返回权限码集合return permissionList;}/*** 返回一个账号所拥有的可用角色标识集合** @param loginId   登录用户id* @param loginType 登录账号类型* @return 角色集合*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {SaSession session = StpUtil.getSessionByLoginId(loginId);return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));}}

后端自定义侦听器

doLogin — 用户登录时触发

  • 功能:每当用户成功登录时触发此方法。
  • 主要步骤
  1. 查询用户信息,包括头像和昵称。
  2. 解析用户的浏览器和操作系统信息(通过 UserAgentUtils.parseOsAndBrowser)。
  3. 获取登录时的 IP 地址,并查询该 IP 的来源地(如城市名)。
  4. 获取当前的登录时间。
  5. 创建一个 OnlineUserResp 对象,包含用户的基本信息、登录 IP、操作系统和浏览器等信息,并将其保存到 SaSession 中。
  6. 更新数据库中的用户信息(如 IP 地址、登录时间等)。
  • 作用:此方法不仅处理了用户登录,还将用户的登录信息(如 IP 地址、设备信息等)存储到会话中,供后续使用。同时更新了数据库中的用户信息,以便后续管理。

总结

  • 功能MySaTokenListener 实现了 SaTokenListener 接口,并提供了用户登录、注销、踢下线等事件的自定义处理。
    • 在登录时,记录用户的登录信息,包括设备、IP 地址、地理位置等,并将这些信息存储到 token 会话中。
    • 在注销时,清除 token 会话中的用户信息。
  • 扩展性:其他事件(如被踢下线、二级认证等)目前没有具体实现,但可以根据业务需求添加逻辑,比如发送通知、更新状态等。
  • 依赖:该实现依赖于 UserMapper(用于查询和更新用户信息)、IpUtils(用于获取 IP 地址的来源)、UserAgentUtils(用于解析用户的操作系统和浏览器信息)等工具类。

完整代码

 /*** 每次登录时触发*/@Overridepublic void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {// 查询用户昵称User user = userMapper.selectOne(new LambdaQueryWrapper<User>().select(User::getAvatar, User::getNickname).eq(User::getId, loginId));// 解析browser和osMap<String, String> userAgentMap = UserAgentUtils.parseOsAndBrowser(request.getHeader("User-Agent"));// 获取登录ip地址String ipAddress = ServletUtil.getClientIP(request);// 获取登录地址String ipSource = IpUtils.getIpSource(ipAddress);// 获取登录时间LocalDateTime loginTime = LocalDateTime.now(ZoneId.of(SHANGHAI.getZone()));OnlineUserResp onlineUserResp = OnlineUserResp.builder().id((Integer) loginId).token(tokenValue).avatar(user.getAvatar()).nickname(user.getNickname()).ipAddress(ipAddress).ipSource(ipSource).os(userAgentMap.get("os")).browser(userAgentMap.get("browser")).loginTime(loginTime).build();// 更新用户登录信息User newUser = User.builder().id((Integer) loginId).ipAddress(ipAddress).ipSource(ipSource).loginTime(loginTime).build();userMapper.updateById(newUser);// 用户在线信息存入tokenSessionSaSession tokenSession = StpUtil.getTokenSessionByToken(tokenValue);tokenSession.set(ONLINE_USER, onlineUserResp);}

相关文章:

后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权

目录 后端注册拦截器 实现对 WebMvcConfigurer 接口的类实现 静态变量 方法重写 注册 Spring Framework拦截器 Sa-Token中SaServletFilter拦截器 思考 为什么使用两个拦截器 1. Spring Framework 拦截器 2. SaServletFilter 为什么要注册两个拦截器&#xff1f; 总结 …...

继承(8)

大家好&#xff0c;今天我们来学习一下继承方式相关的知识&#xff0c;有助于我们对java的继承有更深的了解&#xff0c;话不多说&#xff0c;来看。 1.10 继承方式 在现实生活中,事物之间的关系是非常复杂,灵活多样。 Java中支持以下几种继承方式: 单继承&#xff1a; 多层…...

深度学习-图神经网络-超图概念及在Hyper-YOLO的应用(小白也看懂)

为什么需要超图&#xff1f; 在一个复杂系统中&#xff0c;某些节点&#xff08;实体&#xff09;之间的互动可能不是仅限于两个节点之间的关系&#xff0c;而是多个节点同时参与的更复杂的关系&#xff08;超边&#xff09;。简单说就是为了更好的描述事物之间的关系&#xf…...

django基于Python的校园个人闲置物品换购平台

Django 基于 Python 的校园个人闲置物品换购平台 一、平台概述 Django 基于 Python 的校园个人闲置物品换购平台是专为校园师生打造的一个便捷、环保且充满活力的线上交易场所。它借助 Django 这一强大的 Python Web 开发框架&#xff0c;整合了校园内丰富的闲置物品资源&…...

opencv的NLM去噪算法

NLM&#xff08;Non-Local Means&#xff09;去噪算法是一种基于图像块&#xff08;patch&#xff09;相似性的去噪方法。其基本原理是&#xff1a; 图像块相似性&#xff1a;算法首先定义了一个搜索窗口&#xff08;search window&#xff09;&#xff0c;然后在该窗口内寻找…...

嵌入式系统中的 OpenCV 与 OpenGLES 协同应用

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 一、OpenCV 在嵌入式中的基石地位二、OpenGLES 为嵌入式图形渲染赋能三、二者协同的精妙之处四、面临的挑战与应对策略 在嵌入式开…...

第三十六章 Spring之假如让你来写MVC——拦截器篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…...

DDD - 如何运用 DDD 进行数据库设计

文章目录 Pre概述领域对象持久化的思想领域模型的设计传统的 4 种关系1. 一对一关系2. 多对一关系3. 一对多关系4. 多对多关系 继承关系的 3 种设计1. 继承关系的第一种方案&#xff1a;整个父类与子类都写入一张表2. 继承关系的第二种方案&#xff1a;各子类各自对应各自的表3…...

OSPF - 特殊报文与ospf的机制

&#x1f460;1 携带FA地址的5类LSA 除去7类转5类的LSA会携带FA地址&#xff0c;还有一种情况会有FA地址 FA地址:forwarding address 转发地址&#xff0c;解决次优路径&#xff0c;避免环路5类LSA FA地址不为0&#xff0c;则直接通过FA地址去往目标网段 FA地址为0&#xff0c…...

VSCode 插件

VSCode 插件 1. GitHub Copilot - AI 代码助手 功能&#xff1a;根据上下文提供实时代码补全&#xff0c;支持自然语言转代码&#xff0c;提供符合现代编程规范的建议。进阶技巧&#xff1a; 使用快捷键 Alt ] 切换多个建议。写注释时&#xff0c;描述业务逻辑而不是具体实现…...

jQuery CSS 类

jQuery CSS 类 引言 在网页设计和开发中&#xff0c;CSS&#xff08;层叠样式表&#xff09;起着至关重要的作用&#xff0c;它负责定义网页的布局、颜色、字体等视觉效果。jQuery&#xff0c;作为一个快速、小巧且功能丰富的JavaScript库&#xff0c;极大地简化了HTML文档的…...

CentOS下安装Docker

Docker 必须要在Linux环境下才能运行&#xff0c;windows下运行也是安装虚拟机后才能下载安装运行&#xff0c;菜鸟教程 下载安装 linux 依次执行下边步骤 更新 yum yum update 卸载旧的Docker yum remove docker docker-client docker-client-latest docker-common doc…...

SQLAlchemy

https://docs.sqlalchemy.org.cn/en/20/orm/quickstart.htmlhttps://docs.sqlalchemy.org.cn/en/20/orm/quickstart.html 声明模型 在这里&#xff0c;我们定义模块级构造&#xff0c;这些构造将构成我们从数据库中查询的结构。这种结构被称为 声明式映射&#xff0c;它同时定…...

2025年第三届“华数杯”国际赛A题解题思路与代码(Python版)

游泳竞技策略优化模型代码详解 第一题&#xff1a;速度优化模型 在这一部分&#xff0c;我们将详细解析如何通过数学建模来优化游泳运动员在不同距离比赛中的速度分配策略。 1. 模型概述 我们的模型主要包含三个核心文件&#xff1a; speed_optimization.py: 速度优化的核…...

深入架构剖析 博客点赞逻辑 strategy 策略模式 策略接口 上下文 具体策略 项目实战

目录 点赞策略上下文 策略上下文代码详解 1. 策略模式概述 2. 核心组件 3. 代码解读 LikeStrategyContext 类 LikeTypeEnum 枚举 LikeStrategy 接口 具体策略类 4. 如何使用这个设计 5. 优点 6. 总结 具体代码实现 定义枚举类 从控制层传入参数到上下文 在策略上…...

嵌入式Linux之C语言开发基础

一、C 语言编译过程 Linux 的 C 语言开发&#xff0c;一般选择 GCC 工具链进行编译&#xff0c;示例&#xff1a; 1.mkdir helloworld 2.cd helloworld // 1.main.c #include "hello.h" int main() {say_hello();return 0; } // 2.hello.h #ifndef __HELLO_H__ #de…...

std::accumulate

std::accumulate 是 C 标准库中的一个算法&#xff0c;定义在 <numeric> 头文件中。它用于计算给定范围内元素的累积值&#xff08;通常是一个和&#xff0c;但也可以是其他类型的累积操作&#xff09;。 template< class InputIt, class T > T accumulate( Input…...

计算机网络 (33)传输控制协议TCP概述

一、定义与基本概念 TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。它工作在OSI模型的第四层&#xff0c;即传输层&#xff0c;为用户提供可靠的、有序的和无差错的数据传输服务。TCP协议与UDP协议是传输层的两大主要协议&#xff0c;但两者在设计上有明显的不同&…...

Gitee图形界面上传(详细步骤)

目录 1.软件安装 2.安装顺序 3.创建仓库 4.克隆远程仓库到本地电脑 提交代码的三板斧 1.软件安装 Git - Downloads (git-scm.com) Download – TortoiseGit – Windows Shell Interface to Git 2.安装顺序 1. 首先安装git-2.33.1-64-bit.exe&#xff0c;顺序不能搞错2. …...

【STM32-学习笔记-6-】DMA

文章目录 DMAⅠ、DMA框图Ⅱ、DMA基本结构Ⅲ、不同外设的DMA请求Ⅳ、DMA函数Ⅴ、DMA_InitTypeDef结构体参数①、DMA_PeripheralBaseAddr②、DMA_PeripheralDataSize③、DMA_PeripheralInc④、DMA_MemoryBaseAddr⑤、DMA_MemoryDataSize⑥、DMA_MemoryInc⑦、DMA_DIR⑧、DMA_Buff…...

苍穹外卖08——(涉及接收日期格式数据、ApachePOI导出报表、sql获取top10菜品数据)

营业额统计 service层 在需要处理空值、与数据库交互或使用集合时&#xff0c;Integer 、Double是更好的选择。 // 导入string工具类 import org.apache.commons.lang.StringUtils; Service // 标记该类为Spring的服务组件 Slf4j // 引入日志功能 public class Repor…...

Node.js——fs(文件系统)模块

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…...

【Docker】入门教程

目录 一、Docker的安装 二、Docker的命令 Docker命令实验 1.下载镜像 2.启动容器 3.修改页面 4.保存镜像 5.分享社区 三、Docker存储 1.目录挂载 2.卷映射 四、Docker网络 1.容器间相互访问 2.Redis主从同步集群 3.启动MySQL 五、Docker Compose 1.命令式安装 …...

Ubuntu中使用miniconda安装R和R包devtools

安装devtools环境包 sudo apt-get install gfortran -y sudo apt-get install build-essential -y sudo apt-get install libxt-dev -y sudo apt-get install libcurl4-openssl-dev -y sudo apt-get install libxml2.6-dev -y sudo apt-get install libssl-dev -y sudo apt-g…...

大语言模型预训练、微调、RLHF

转发&#xff0c;如有侵权&#xff0c;请联系删除&#xff1a; 1.【LLM】3&#xff1a;从零开始训练大语言模型&#xff08;预训练、微调、RLHF&#xff09; 2.老婆饼里没有老婆&#xff0c;RLHF里也没有真正的RL 3.【大模型微调】一文掌握7种大模型微调的方法 4.基于 Qwen2.…...

啥!GitHub Copilot也免费使用了

文章目录 前言免费版直接修复代码多文件上下文Agent模式总结 前言 最近&#xff0c;GitHub 给开发者们带来了一个好消息&#xff1a;他们的 AI 编程助手 GitHub Copilot 现在可以免费使用了&#xff01;以前&#xff0c;每个月要花 10 美元才能享受的服务&#xff0c;现在对所…...

【Ubuntu与Linux操作系统:五、文件与目录管理】

第5章 磁盘存储管理 5.1 Linux磁盘存储概述 磁盘存储是Linux系统存储数据的重要组件&#xff0c;它通过分区和文件系统组织和管理数据。Linux支持多种文件系统&#xff0c;如ext4、xfs和btrfs&#xff0c;并以块的形式管理存储设备。 1. 分区与文件系统&#xff1a; 分区&am…...

【PDF转Word】 PDF在线转word文档 好用!优质网站资源推荐

大家在工作与学习中&#xff0c;经常需要将PDF文件转换为Word格式以便进行编辑和修改。很多人都不知道怎么操作&#xff0c;今天我们介绍一个非常好用的工具&#xff1a;小白工具网&#xff0c;可以在线帮忙大家快速把PDF转换成word格式。 小白工具网提供的PDF转Word功能&…...

计算机网络 (38)TCP的拥塞控制

前言 TCP拥塞控制是传输控制协议&#xff08;Transmission Control Protocol&#xff0c;TCP&#xff09;避免网络拥塞的算法&#xff0c;是互联网上主要的一个拥塞控制措施。 一、目的 TCP拥塞控制的主要目的是防止过多的数据注入到网络中&#xff0c;使网络能够承受现有的网络…...

构造函数的原型原型链

代码示例 // 定义一个构造函数 Test function Test() {this.name 张三 }; //向构造函数的原型添加一个属性 age18 Test.prototype.age 18;//使用构造函数 Test 来实例化一个新对象 const test new Test();//向 Object.prototype 添加了一个名为 sex 的属性&#xff0c;其值…...

2025华数杯国际赛A题完整论文讲解(含每一问python代码+数据+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025“华数杯”国际大学生数学建模竞赛A题Can He Swim Faster的完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文…...

[RabbitMQ] RabbitMQ运维问题

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

GenAIOps:生成式 AI 运维 - 实用指南

https://medium.com/google-cloud/genaiops-operationalize-generative-ai-a-practical-guide-d5bedaa59d78​​​​​​ 作者&#xff1a;Dr Sokratis Kartakis 从创意到生产&#xff1a;使用生成式 AI 和运维 (GenAIOps) 概述 生成式 AI (GenAI) 的世界充满了令人兴奋的可…...

用户界面软件04

后果 使用这种架构很容易对两个层面的非功能性需求进行优化&#xff0c;但是你仍然需要小心不要将功能 需求重复实现。 现在&#xff0c;两个层面可能有完全不同的设计。比如&#xff0c;用户界面层可能使用配件模型&#xff08;Widget Model&#xff09;&#xff0c; 以大量的…...

分布式系统设计:Java的秘密基地布局

分布式系统设计是Java企业级开发中的一个关键领域&#xff0c;它涉及到构建高可用、可扩展、高性能的系统。以下是分布式系统设计的一些核心概念和实践&#xff1a; 3.1 分布式系统的特点 分布式系统由多个独立的计算机节点组成&#xff0c;这些节点通过网络连接在一起&#x…...

【Redis学习 | 第5篇】Redis缓存 —— 缓存的概念 + 缓存穿透 + 缓存雪崩 + 缓存击穿

文章目录 完成任务1. 什么是缓存2. 添加商户缓存3. 缓存更新策略3.1 主动更新 4. 缓存穿透5. 缓存雪崩6. 缓存击穿6.1 使用互斥锁查询商铺信息6.2 使用逻辑过期查询商铺信息 7. 封装 Redis 工具类 完成任务 1. 什么是缓存 缓存&#xff1a;数据交换的缓冲区&#xff08;Cache…...

MySQL索引覆盖(覆盖索引, Covering Index)

文章目录 说明MySQL索引覆盖&#xff08;覆盖索引, Covering Index&#xff09;覆盖索引的概念覆盖索引的示例示例查询及索引覆盖情况覆盖索引的性能优势覆盖索引的实现条件覆盖索引 vs 非覆盖索引覆盖索引的限制如何设计覆盖索引覆盖索引的实际案例场景 1&#xff1a;电商系统…...

VUE3 provide 和 inject,跨越多层级组件传递数据

provide 和 inject 是 Vue 3 提供的 API&#xff0c;主要用于实现祖先组件与后代组件之间的依赖注入。它们可以让你在组件树中&#xff0c;跨越多层组件传递数据&#xff0c;而不需要通过 props 或事件的方式逐层传递。这个机制主要用于状态共享、插件系统或某些跨层级的功能。…...

【UE5 C++课程系列笔记】29——在UE中使用第三方库的流程

目录 前言 步骤 一、新建插件 二、创建第三方库 三、使用第三方库 前言 主要就是介绍如何将普通C++工程生成的头文件和.dll导入到UE中去使用。 步骤 一、新建插件 1. 打开插件浏览器选项卡 2. 打开插件创建器 3. 选择“第三方库”,这里命名为“MyThirdPartyLibrary…...

Type-C双屏显示器方案

在数字化时代&#xff0c;高效的信息处理和视觉体验已成为我们日常生活和工作的关键需求。随着科技的进步&#xff0c;一款结合了便携性和高效视觉输出的设备——双屏便携屏&#xff0c;逐渐崭露头角&#xff0c;成为追求高效工作和娱乐体验人群的新宠。本文将深入探讨双屏便携…...

20250112面试鸭特训营第20天

更多特训营笔记详见个人主页【面试鸭特训营】专栏 250112 1. TCP 和 UDP 有什么区别&#xff1f; 特性TCPUDP连接方式面向连接&#xff08;需要建立连接&#xff09;无连接&#xff08;无需建立连接&#xff09;可靠性可靠的&#xff0c;提供确认、重传机制不可靠&#xff0c…...

使用conda出现requests.exceptions.HTTPError 解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

玩转大语言模型——langchain调用ollama视觉多模态语言模型

系列文章目录 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 langchain调用ollama视觉多模态语言模型 系列文章目录前言使用Ollama下载模型查找模型下载模型 测试模型ollama测试langchain测试加载图片加载模型…...

【玩转MacBook】mdfind命令搜索

mdfind 是 macOS 上的一个命令行工具&#xff0c;它允许用户根据元数据来查找文件。mdfind 使用 Spotlight 索引来快速搜索文件系统中的项目。这意味着它可以非常快地找到文件&#xff0c;因为它不直接在磁盘上搜索&#xff0c;而是查询由 Spotlight 维护的索引数据库。 基本用…...

数据结构与算法之二叉树: LeetCode 637. 二叉树的层平均值 (Ts版)

二叉树的层平均值 https://leetcode.cn/problems/average-of-levels-in-binary-tree/description/ 描述 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值与实际答案相差 1 0 − 5 10^{-5} 10−5 以内的答案可以被接受 示例 1 输入&#xff1a;root…...

【巨实用】Git客户端基本操作

本文主要分享Git的一些基本常规操作&#xff0c;手把手教你如何配置~ ● 一个文件夹中初始化Git git init ● 为了方便以后提交代码需要对git进行配置&#xff08;第一次使用或者需求变更的时候&#xff09;&#xff0c;告诉git未来是谁在提交代码 git config --global user.na…...

从预训练的BERT中提取Embedding

文章目录 背景前置准备思路利用Transformer 库实现 背景 假设要执行一项情感分析任务&#xff0c;样本数据如下 可以看到几个句子及其对应的标签&#xff0c;其中1表示正面情绪&#xff0c;0表示负面情绪。我们可以利用给定的数据集训练一个分类器&#xff0c;对句子所表达的…...

BGP 泄露

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 目录 1. BGP 是什么&#xff1f; 2. 什么是 BGP 泄露&#xff1f; 3. 今天发生了什么&#xff1f; 4. 正常和被劫持状态下的路由示意图 5. 受影响区域 6. 责任在谁&#xff1f; 7. 有办法避免这…...

IntelliJ IDEA和MAVEN基本操作:项目和缓存存储到非C盘

为了将 IntelliJ IDEA 的所有项目和缓存存储到 C 盘以外的地方&#xff0c;以下是你需要调整的设置和步骤&#xff1a; 1. 更改项目默认存储位置 打开 IntelliJ IDEA。点击顶部菜单的 File > Settings &#xff08;Windows&#xff09;或 IntelliJ IDEA > Preferences &…...

Leetcode 3418. Maximum Amount of Money Robot Can Earn

Leetcode 3418. Maximum Amount of Money Robot Can Earn 1. 解题思路2. 代码实现 题目链接&#xff1a;3418. Maximum Amount of Money Robot Can Earn 1. 解题思路 这一题我的思路比较暴力&#xff0c;就是一个动态规划&#xff0c;本质上就是全量遍历&#xff0c;然后找到…...