微服务项目->在线oj系统(Java-Spring)----6.0

微服务项目->在线oj系统(Java-Spring)----6.0

创建token

在oj-common-security中引入依赖

创建jwt⼯具类:

package com.bite.common.security.utils; import com.bite.common.core.comtains.JwtContains; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.HashMap; import java.util.Map; public class JwtUtils { /** * 生成令牌 * * @param claims 数据 * @param secret 密钥 * @return 令牌 */ public static String createToken(Map<String, Object> claims, String secret) { String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret) .compact(); return token; } /** * 从令牌中获取数据 * * @param token 令牌 * @param secret 密钥 * @return 数据 */ public static Claims parseToken(String token, String secret) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } } 

createToken

----根据所传的claims和secret创建一个token

注意:这里的secret需要保密随机,不能硬编码,可以定期更换

(所以可以采用nacos来配置secret,这个后面细讲)

硬编码(Hard Coding)是指在程序代码中直接嵌入固定数值、字符串或逻辑,而非通过变量、配置文件或外部输入来动态获取的编程方式。这种做法会导致代码灵活性降低,维护成本增加。以下是关于硬编码的详细解析:

-

里面的这个SignatureAlgorithm.HS512是创建所使用的算法

使用例子:

首先创建一个token,这里由于是实例,所以密钥随便即可

 public static void main(String[] args) { Map<String,Object> claim=new HashMap<>(); claim.put("userid",123456); System.out.println(createToken(claim,"ddwdwdwdw5dfw5")); } 

获取到到token后通过JwtUtil的parseToken进行解码(需要token和secret)

 public static void main(String[] args) { String token="eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw"; Claims claims=parseToken("eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw","ddwdwdwdw5dfw5"); System.out.println(claims); } 

目的

在OJ-modules中引入oj-common-security

创建常量类

 public class CacheConstants { public final static String LOGIN_TOKEN_KEY = "login_tokens:"; public final static long EXPIRATION = 720; } 
public class JwtContains { public static final String LOGIN_USER_ID="userId"; public static final String LOGIN_USER_KEY="userKey"; } 

定义登录用户

@Data public class LoginUser { private Integer identity; //普通用户1 管理员2 } 

引入依赖

 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool-all.version}</version> </dependency> <dependency> <groupId>com.bite</groupId> <artifactId>oj-common-redis</artifactId> <version>${oj-common-redis.version}</version> <scope>compile</scope> </dependency>

定义TokenService类

@Service public class TokenService { @Autowired private RedisService redisService; /** * 创建令牌 */ public String createToken(Long userId,String secret,Integer identity) { String userKey = UUID.fastUUID().toString(); // Jwt存储信息 Map<String, Object> claim = new HashMap<String, Object>(); claim.put(JwtContains.LOGIN_USER_ID,userId); claim.put(JwtContains.LOGIN_USER_KEY,userKey); String token=JwtUtils.createToken(claim,secret); String key=CacheConstants.LOGIN_TOKEN_KEY+userKey; LoginUser loginUser=new LoginUser(); loginUser.setIdentity(UserIdentity.ADMIN.getCode()); redisService.setCacheObject(key, loginUser, CacheConstants.EXPIRATION, TimeUnit.MINUTES); return token; } } 

核心代码详解:

首先我们通过UUID来获取一个唯一的userKey

创建一个claim,存放着用户的userId以及刚刚创建的userKey;

这个是调用方传入通过nacos得到的密钥,我们需要传给createToken

我们通过claim,secret来生成一个token.

然后我们创建一个变量key(其实就是一个常量+userKey);

然后将key存入redis中,value是用户身份

对login接口中Service部分进行修改

对oj-common-security项目进行修改

身份认证

引入依赖

填入白名单

创建一个白名单类,从nacos中去获取哪些是白名单中的url

@Configuration//配置类 @RefreshScope//实时刷新,使nacos上面的改变立刻生效 @ConfigurationProperties(prefix = "security.ignore") // public class IgnoreWhiteProperties { /** * 放⾏⽩名单配置,⽹关不校验此处的⽩名单 */ private List<String> whites = new ArrayList<>(); public List<String> getWhites() { //拿到nacos配置里面的白名单 return whites; } public void setWhites(List<String> whites) { this.whites = whites; } }

修改nacos

自定义过滤器

import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.bite.common.core.comtains.CacheConstants; import com.bite.common.core.comtains.HttpConstants; import com.bite.common.core.domain.R; import com.bite.common.core.enums.ResultCode; import com.bite.common.core.enums.UserIdentity; import com.bite.common.redis.service.RedisService; import com.bite.common.security.model.LoginUser; import com.bite.common.security.utils.JwtUtils; import io.jsonwebtoken.Claims; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.util.CollectionUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.List; /** * ⽹关鉴权 * */ @Slf4j @Component public class AuthFilter implements GlobalFilter, Ordered { //实现GlobalFilter相当于实现了一个自定义过滤器 //实现Order是为什么以后有多个过滤器的时候进行先后排序 // 排除过滤的 uri ⽩名单地址,在nacos⾃⾏添加 @Autowired private IgnoreWhiteProperties ignoreWhite; @Value("${jwt.secret}") private String secret; @Autowired private RedisService redisService; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); //获取请求 String url = request.getURI().getPath(); // 跳过不需要验证的路径 if (matches(url, ignoreWhite.getWhites())) { return chain.filter(exchange); //如果是白名单里面的,则跳过身份认证 } //从http请求头中获取token String token = getToken(request); //判断token是否为空,如果为空说明以前未登录或者登录超市; if (StrUtil.isEmpty(token)) { return unauthorizedResponse(exchange, "令牌不能为空"); } Claims claims; try { claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息 if (claims == null) { return unauthorizedResponse(exchange, "令牌已过期或验证不正确!"); } } catch (Exception e) { return unauthorizedResponse(exchange, "令牌已过期或验证不正确!"); } String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key boolean isLogin = redisService.hasKey(getTokenKey(userKey));//用得到的key去Redis中查找,如果找不到说明过期了 if (!isLogin) { return unauthorizedResponse(exchange, "登录状态已过期"); } String userid = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整 if (StrUtil.isEmpty(userid)) { return unauthorizedResponse(exchange, "令牌验证失败"); } //c端用户只能请求C端功能,B端用户只能请求管理端功能 LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);//通过key去Redis里面查找value(存着用户是管理员还是用户) if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getCode().equals(user.getIdentity())) { return unauthorizedResponse(exchange, "令牌验证失败"); } if (url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getCode().equals(user.getIdentity())) { return unauthorizedResponse(exchange, "令牌验证失败"); } return chain.filter(exchange); } /** * 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串 * * @param url 指定url * @param patternList 需要检查的匹配规则链表 * @return 是否匹配 */ private boolean matches(String url, List<String> patternList) { if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) { return false; } for (String pattern : patternList) { //从白名单里面分别列出白名单里面的访问地址 //判断有没有匹配的 if (isMatch(pattern, url)) { return true; } } return false; } /** * 判断url是否与规则匹配 * 匹配规则中: * ? 表⽰单个字符; * * 表⽰⼀层路径内的任意字符串,不可跨层级; * ** 表⽰任意层路径; * * @param pattern 匹配规则 * @param url 需要匹配的url * @return 是否匹配 */ private boolean isMatch(String pattern, String url) { AntPathMatcher matcher = new AntPathMatcher(); //将pattern作为匹配规则 return matcher.match(pattern, url); } /** * 获取缓存key */ private String getTokenKey(String token) { return CacheConstants.LOGIN_TOKEN_KEY + token; } /** * 从请求头中获取请求token */ private String getToken(ServerHttpRequest request) { String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION); // 如果前端设置了令牌前缀,则裁剪掉前缀 if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) { token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY); } return token; } private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) { log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode()); } //拼装webflux模型响应 private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) { response.setStatusCode(HttpStatus.OK); response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); R<?> result = R.fail(code, msg); DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes()); return response.writeWith(Mono.just(dataBuffer)); } @Override public int getOrder() { return -200; //定义的值越小越靠前执行 } } 

不懂的地方看代码注释

这里为什么不抛出异常,然后之前全局异常捕获?

这是因为@RestControllerAdvice只对springweb起作用,而springcloud gataway属于webflux

RedisConfig修改需要增加@AutoConfigureBefore(RedisAutoConfiguration.class)注解否则会报错如下:

让spring优先注册我们这个RedisTemplate

配置secret

配置redis

然后我们测试一下:

Read more

AgentScope Java 入门到进阶

一、什么是 AgentScope Java? AgentScope Java 是一个面向智能体的编程框架,用于构建基于大语言模型(LLM)的应用。它提供了构建智能体所需的一切功能:ReAct 推理、工具调用、记忆管理、多智能体协作等,让你能够快速开发生产级的 AI 智能体应用。 二、环境准备 1. 系统要求 * JDK 17 或更高版本 * Maven 或 Gradle 构建工具 2. 添加依赖 在你的 pom.xml 中添加以下依赖: <dependency><groupId>io.agentscope</groupId><artifactId>

By Ne0inhk
Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:RocketMQ 顺序消息(全局 / 分区顺序) * 什么是顺序消息? * RocketMQ 顺序消息的工作原理 * 全局顺序 vs 分区顺序 * RocketMQ 顺序消息的核心机制 * 全局顺序消息的实现 * 全局顺序的配置要求 * Java 代码示例:全局顺序消息 * 全局顺序的局限性 * 分区顺序消息的实现 * 分区顺序的设计思路 * Java 代码示例:分区顺序消息 * 分区顺序的关键要点 * 顺序消息的消费机制详解 * ConsumeOrderlyStatus 枚举 * 消费失败的处理机制 * 并发消费 vs 顺序消费

By Ne0inhk
基于 Java 的消息队列选型年度总结:RabbitMQ、RocketMQ、Kafka 实战对比

基于 Java 的消息队列选型年度总结:RabbitMQ、RocketMQ、Kafka 实战对比

文章目录 * 基于 Java 的消息队列选型年度总结:RabbitMQ、RocketMQ、Kafka 实战对比 🚀 * 一、为什么需要消息队列?🤔 * 二、三大消息队列详解 📚 * 1. RabbitMQ * 2. RocketMQ * 3. Apache Kafka * 三、三大消息队列概览 📊 * 四、架构设计对比 🏗️ * 1. RabbitMQ 架构 * 2. RocketMQ 架构 * 3. Kafka 架构 * 五、Java 集成实战 💻 * 1. RabbitMQ + Spring Boot 示例 * 2. RocketMQ + Spring Boot 示例 * 3. Kafka + Spring Boot 示例

By Ne0inhk
双非 Java 后端首次实习 | 个人经验分享总结

双非 Java 后端首次实习 | 个人经验分享总结

摘要:实习期间参与企业后台项目开发,熟悉企业开发流程与代码规范。 实习核心流程(结合实际经历) 由于自己进入的是一个小公司实习,当时项目刚好启动,参与了较多基础模块的开发。 一、基础准备与环境搭建阶段(入职 1-3 天) 1. 公司基础配置:进入公司飞书、拥有个人邮箱等基础办公权限 2. 代码拉取与环境搭建: * 学习并使用 git/svn 等版本管理工具 clone 项目代码(公司使用的是阿里云云效) * 配置项目所需配置文件,搭建后端 + 前端开发环境(后端需兼顾前端环境) * 解决环境依赖问题,确保项目能正常跑起来(熟悉配置文件与环境) * 熟悉开发工具的使用,避免因操作问题浪费时间(mentor 教了debug技巧,快捷键) 二、项目熟悉阶段(入职 1-2 周) 这个阶段任务主要是熟悉环境,熟练使用通用封装 / 工具类,自己在熟悉项目的时候,寻找少量项目bug,提交问题给 mentor

By Ne0inhk