多种语言网站怎么做,高科技公司网站模板,网站内容的实现方式,wordpress菜伪静态文章目录 #x1f31e; Sun Frame#xff1a;SpringBoot 的轻量级开发框架#xff08;个人开源项目推荐#xff09;#x1f31f; 亮点功能#x1f4e6; spring cloud模块概览常用工具 #x1f517; 更多信息1.设计1.链路流程2.详细设计 2.网关过滤器获取唯一标识放到Hea… 文章目录 Sun FrameSpringBoot 的轻量级开发框架个人开源项目推荐 亮点功能 spring cloud模块概览常用工具 更多信息1.设计1.链路流程2.详细设计 2.网关过滤器获取唯一标识放到Header1.LoginFilter.java2.SubjectCategoryController.java 测试3.进行测试1.用户登录2.携带token访问后端接口 3.基于ThreadLocal实现上下文传递1.目录结构2.ThreadLocal的工具类 LoginContextHolder.java3.拦截器将网关传递的信息放到 LoginInterceptor.java4.配置类将自定义拦截器注册 GlobalConfig.java5.LoginUtil.java6.测试 SubjectCategoryController.java 4.OpenFeign的使用1.目录结构1.sun-club-auth 2.sun-club-auth-api引入依赖3.UserFeignService.java 将controller层的接口暴露4.AuthUserDTO.java 从controller剪切到api层5.Result.java和ResultCodeEnum.java从common包中剪切过来6.sun-club-auth-application-controller 引入api层的依赖DTO和Result都使用api层的7.sun-club-subject微服务调用sun-club-auth微服务1.sun-club-infra引入sun-club-auth微服务的api包2.在sun-club-infra模块的UserRpc.java进行rpc调用3.创建接受信息的entityUserInfo.java4.在启动类加注解启动Feign5.测试TestFeignController.java6.测试 5.OpenFeign拦截器打通微服务上下文由调用方来写1.FeignRequestInterceptor.java 将唯一标识放到Header中传递到其他微服务2.FeignConfiguration.java 将拦截器注入容器3.在被调用方使用过滤器将用户唯一标识存到ThreadLocal跟前面写过的一样1.目录结构2.ThreadLocal的工具类LoginContextHolder.java3.自定义拦截器获取Header中的唯一标识并放到ThreadLocal中 LoginInterceptor.java4.将拦截器注入容器 GlobalConfig.java5.UserController.java 测试获取调用方传来的唯一标识 4.用户上下文整体流程测试1.网关过滤器将唯一标识从redis中取出放到Header传到微服务模块2.Feign的调用微服务的登录拦截器将唯一标识放到ThreadLocal中3.在调用Feign之前进入Feign拦截器将唯一标识放到Header中传递给被调用方4.被调用方的登录拦截器读取Header中的唯一标识放到ThreadLocal中5.被调用方从ThreadLocal中可以获取到唯一标识 6.本地缓存guava使用1.SubjectCategoryDomainServiceImpl.java2.测试3.本地缓存工具类1.ListLocalCacheUtil.java2.使用方式1.依赖注入2.使用3.测试 Sun FrameSpringBoot 的轻量级开发框架个人开源项目推荐 轻松高效的现代化开发体验 Sun Frame 是我个人开源的一款基于 SpringBoot 的轻量级框架专为中小型企业设计。它提供了一种快速、简单且易于扩展的开发方式。 我们的开发文档记录了整个项目从0到1的任何细节实属不易请给我们一个Star您的支持是我们持续改进的动力。 亮点功能
组件化开发灵活选择简化流程。高性能通过异步日志和 Redis 缓存提升性能。易扩展支持多种数据库和消息队列。 spring cloud模块概览
Nacos 服务高效的服务注册与发现。Feign 远程调用简化服务间通信。强大网关路由与限流。
常用工具
日志管理异步处理与链路追踪。Redis 集成支持分布式锁与缓存。Swagger 文档便捷的 API 入口。测试支持SpringBoot-Test 集成。EasyCode自定义EasyCode模板引擎一键生成CRUD。 更多信息
开源地址Gitee Sun Frame详细文档语雀文档 1.设计
1.链路流程 2.详细设计 2.网关过滤器获取唯一标识放到Header
1.LoginFilter.java
package com.sunxiansheng.club.gateway.filter;import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** Description: 网关过滤器在用户登录之后再次请求网关就会携带token可以通过网关过滤器通过redis获取用户的唯一标识然后放到Header中传递到后端* 过滤器的优先级是比配置文件中配置的路由要低* Author sun* Create 2024/6/15 15:28* Version 1.0*/
Component
Slf4j
public class LoginFilter implements GlobalFilter {OverrideSneakyThrowspublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求ServerHttpRequest request exchange.getRequest();// 获取一个很全的东西ServerHttpRequest.Builder mutate request.mutate();// 获取urlString url request.getURI().getPath();log.info(网关过滤器用户请求的url url);// 如果发现是用户登录的请求直接放行注意先走的网关配置所以前缀会被删除if (url.equals(/user/doLogin)) {return chain.filter(exchange);}// 通过sa-token框架获取用户的tokenInfo可以通过这个对象来获取用户信息SaTokenInfo tokenInfo StpUtil.getTokenInfo();// 根据token获取用户的唯一标识String loginId (String) tokenInfo.getLoginId();if (StringUtils.isBlank(loginId)) {log.info(网关过滤器获取用户唯一标识失败);}// 如果到这了就说明已经获取到了用户的唯一标识将其放到Header中mutate.header(loginId, loginId);// 将携带了用户唯一标识的request发送到其他微服务模块return chain.filter(exchange.mutate().request(mutate.build()).build());}
}2.SubjectCategoryController.java 测试 3.进行测试
1.用户登录
2.携带token访问后端接口 3.基于ThreadLocal实现上下文传递
1.目录结构 2.ThreadLocal的工具类 LoginContextHolder.java
package com.sunxiansheng.subject.application.context;import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;/*** Description: 上下文对象ThreadLocal* Author sun* Create 2024/6/15 16:27* Version 1.0*/
public class LoginContextHolder {// 这个ThreadLocal持有一个Mapprivate static final InheritableThreadLocalMapString, Object THREAD_LOCAL new InheritableThreadLocal();/*** 为ThreadLocal持有的Map设值* param key* param val*/public static void set(String key, Object val) {MapString, Object map getThreadLocalMap();map.put(key, val);}/*** 从ThreadLocal持有的Map取值* param key* return*/public static Object get(String key) {MapString, Object map THREAD_LOCAL.get();return map.get(key);}/*** 清除ThreadLocal*/public static void remove() {THREAD_LOCAL.remove();}/*** 初始化一个ThreadLocal持有的Map要保证这个Map是单例的* return*/public static MapString, Object getThreadLocalMap() {// 获取到ThreadLocal的MapMapString, Object map THREAD_LOCAL.get();// 如果是空的再创建一个Map然后放进去if (Objects.isNull(map)) {map new ConcurrentHashMap();THREAD_LOCAL.set(map);}// 放到ThreadLocal中return map;}// 以下为获取用户信息的方法public static String getLoginId() {return (String) getThreadLocalMap().get(loginId);}}3.拦截器将网关传递的信息放到 LoginInterceptor.java
package com.sunxiansheng.subject.application.interceptor;import com.sunxiansheng.subject.application.context.LoginContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Description: 处理用户上下文的拦截器* Author sun* Create 2024/6/15 16:20* Version 1.0*/
public class LoginInterceptor implements HandlerInterceptor {/*** 当请求到这里了就说明网关已经将用户的loginId放到了Header里了* param request* param response* param handler* return* throws Exception*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String loginId request.getHeader(loginId);// 将loginId放到ThreadLocal里面LoginContextHolder.set(loginId, loginId);return true;}/*** 在操作结束后清除ThreadLocal因为如果线程复用并且没清除这个ThreadLocal还会存在造成数据污染* param request* param response* param handler* param ex* throws Exception*/Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {LoginContextHolder.remove();}
}4.配置类将自定义拦截器注册 GlobalConfig.java 5.LoginUtil.java
package com.sunxiansheng.subject.application.util;import com.sunxiansheng.subject.application.context.LoginContextHolder;/*** Description: 用户登录的util* Author sun* Create 2024/6/15 17:12* Version 1.0*/
public class LoginUtil {/*获取loginId*/public static String getLoginId() {return LoginContextHolder.getLoginId();}
}6.测试 SubjectCategoryController.java 4.OpenFeign的使用
1.目录结构
1.sun-club-auth 2.sun-club-auth-api引入依赖
dependencies!-- OpenFeign依赖 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactIdversion3.0.7/version/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactIdversion3.0.6/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.16/version/dependency
/dependencies3.UserFeignService.java 将controller层的接口暴露
package com.sunxiansheng.auth.api;import com.sunxiansheng.auth.entity.AuthUserDTO;
import com.sunxiansheng.auth.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;/*** Description: 用户服务feign* Author sun* Create 2024/6/16 13:20* Version 1.0*/
FeignClient(sub-club-auth) // 服务名
public interface UserFeignService {RequestMapping(/user/getUserInfo)public ResultAuthUserDTO getUserInfo(RequestBody AuthUserDTO authUserDTO);
}4.AuthUserDTO.java 从controller剪切到api层
5.Result.java和ResultCodeEnum.java从common包中剪切过来
6.sun-club-auth-application-controller 引入api层的依赖DTO和Result都使用api层的
!-- 引入api层 --
dependencygroupIdcom.sun.club/groupIdartifactIdsun-club-auth-api/artifactIdversion1.0-SNAPSHOT/versionscopecompile/scope
/dependency7.sun-club-subject微服务调用sun-club-auth微服务
1.sun-club-infra引入sun-club-auth微服务的api包
!-- 引入鉴权模块的api层用于微服务间的调用 --
dependencygroupIdcom.sun.club/groupIdartifactIdsun-club-auth-api/artifactIdversion1.0-SNAPSHOT/version
/dependency2.在sun-club-infra模块的UserRpc.java进行rpc调用
package com.sunxiansheng.subject.infra.rpc;import com.sunxiansheng.auth.api.UserFeignService;
import com.sunxiansheng.auth.entity.AuthUserDTO;
import com.sunxiansheng.auth.entity.Result;
import com.sunxiansheng.subject.infra.eneity.UserInfo;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** Description:* Author sun* Create 2024/6/16 13:45* Version 1.0*/
Component
public class UserRpc {// 直接注入FeignServiceResourceprivate UserFeignService userFeignService;/*** 根据用户名来获取用户信息* param userName* return*/public UserInfo getUserInfo(String userName) {AuthUserDTO authUserDTO new AuthUserDTO();authUserDTO.setUserName(userName);// 向调用方法一样远程调用auth微服务的接口并得到结果ResultAuthUserDTO result userFeignService.getUserInfo(authUserDTO);// 创建一个自己的eneity用于存放dataUserInfo userInfo new UserInfo();// 没有成功响应直接返回一个空的对象if (!result.getSuccess()) {return userInfo;}// 成功获取信息将获取到的信息放到自己定义的Entity中// 获取到dataAuthUserDTO data result.getData();// 将data中的数据封装到自己定义的entity中userInfo.setUserName(data.getUserName());userInfo.setNickName(data.getNickName());return userInfo;}
}3.创建接受信息的entityUserInfo.java
package com.sunxiansheng.subject.infra.eneity;import lombok.Data;/*** Description:* Author sun* Create 2024/6/16 13:47* Version 1.0*/
Data
public class UserInfo {private String userName;private String nickName;
}4.在启动类加注解启动Feign 5.测试TestFeignController.java
package com.sunxiansheng.subject.application.controller;import com.sunxiansheng.subject.infra.eneity.UserInfo;
import com.sunxiansheng.subject.infra.rpc.UserRpc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** Description: 题目分类控制器* Author sun* Create 2024/5/24 9:33* Version 1.0*/
RestController
RequestMapping(/subject/category)
Slf4j
public class TestFeignController {Resourceprivate UserRpc userRpc;RequestMapping(/testFeign)public void testFeign() {UserInfo userInfo userRpc.getUserInfo(鸡翅);log.info(testFeign: userInfo);}
}6.测试 5.OpenFeign拦截器打通微服务上下文由调用方来写
1.FeignRequestInterceptor.java 将唯一标识放到Header中传递到其他微服务
package com.sunxiansheng.subject.application.interceptor;import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.Objects;/*** Description: feign的拦截器当使用feign调用另一个微服务时需要将用户的唯一标识放到Header中* Author sun* Create 2024/6/16 15:01* Version 1.0*/
Component
public class FeignRequestInterceptor implements RequestInterceptor {Overridepublic void apply(RequestTemplate requestTemplate) {// 首先从网关过来的请求在Header中一定存着用户的唯一标识从请求中获取ServletRequestAttributes requestAttributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request requestAttributes.getRequest();if (Objects.nonNull(request)) {// 从请求获取唯一标识String loginId request.getHeader(loginId);if (StringUtils.isNotBlank(loginId)) {// 如果获得的标识不为空就将其放到header中requestTemplate.header(loginId, loginId);}}}
}2.FeignConfiguration.java 将拦截器注入容器
package com.sunxiansheng.subject.application.config;import com.sunxiansheng.subject.application.interceptor.FeignRequestInterceptor;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;/*** Description:* Author sun* Create 2024/6/16 15:12* Version 1.0*/
Configuration
public class FeignConfiguration extends WebMvcConfigurationSupport {// 将Feign的拦截器注入进去Beanpublic RequestInterceptor requestInterceptor() {return new FeignRequestInterceptor();}
}3.在被调用方使用过滤器将用户唯一标识存到ThreadLocal跟前面写过的一样
1.目录结构 2.ThreadLocal的工具类LoginContextHolder.java
package com.sunxiansheng.auth.application.context;import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;/*** Description: 上下文对象ThreadLocal* Author sun* Create 2024/6/15 16:27* Version 1.0*/
public class LoginContextHolder {// 这个ThreadLocal持有一个Mapprivate static final InheritableThreadLocalMapString, Object THREAD_LOCAL new InheritableThreadLocal();/*** 为ThreadLocal持有的Map设值* param key* param val*/public static void set(String key, Object val) {MapString, Object map getThreadLocalMap();map.put(key, val);}/*** 从ThreadLocal持有的Map取值* param key* return*/public static Object get(String key) {MapString, Object map THREAD_LOCAL.get();return map.get(key);}/*** 清除ThreadLocal*/public static void remove() {THREAD_LOCAL.remove();}/*** 初始化一个ThreadLocal持有的Map要保证这个Map是单例的* return*/public static MapString, Object getThreadLocalMap() {// 获取到ThreadLocal的MapMapString, Object map THREAD_LOCAL.get();// 如果是空的再创建一个Map然后放进去if (Objects.isNull(map)) {map new ConcurrentHashMap();THREAD_LOCAL.set(map);}// 放到ThreadLocal中return map;}// 以下为获取用户信息的方法public static String getLoginId() {return (String) getThreadLocalMap().get(loginId);}}3.自定义拦截器获取Header中的唯一标识并放到ThreadLocal中 LoginInterceptor.java
package com.sunxiansheng.auth.application.interceptor;import com.sunxiansheng.auth.application.context.LoginContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Description: 处理用户上下文的拦截器* Author sun* Create 2024/6/15 16:20* Version 1.0*/
public class LoginInterceptor implements HandlerInterceptor {/*** 当请求到这里了就说明网关已经将用户的loginId放到了Header里了* param request* param response* param handler* return* throws Exception*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String loginId request.getHeader(loginId);// 将loginId放到ThreadLocal里面LoginContextHolder.set(loginId, loginId);return true;}/*** 在操作结束后清除ThreadLocal因为如果线程复用并且没清除这个ThreadLocal还会存在造成数据污染* param request* param response* param handler* param ex* throws Exception*/Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {LoginContextHolder.remove();}
}4.将拦截器注入容器 GlobalConfig.java 5.UserController.java 测试获取调用方传来的唯一标识 4.用户上下文整体流程测试
1.网关过滤器将唯一标识从redis中取出放到Header传到微服务模块 2.Feign的调用微服务的登录拦截器将唯一标识放到ThreadLocal中 3.在调用Feign之前进入Feign拦截器将唯一标识放到Header中传递给被调用方 4.被调用方的登录拦截器读取Header中的唯一标识放到ThreadLocal中 5.被调用方从ThreadLocal中可以获取到唯一标识 6.本地缓存guava使用
1.SubjectCategoryDomainServiceImpl.java
/*** 注意这里使用本地缓存的原因是分类和标签的改动不大* param subjectCategoryBO* return*/
Override
public ListSubjectCategoryBO queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {// 构建一个本地缓存的keyString cacheKey categoryAndLabel. subjectCategoryBO.getId();// 根据缓存key来获取id对应的分类列表String content localCache.getIfPresent(cacheKey);// 如果缓存中没有则从数据库中查ListSubjectCategoryBO subjectCategoryBOS new LinkedList();if (StringUtils.isBlank(content)) {subjectCategoryBOS getSubjectCategoryBOS(subjectCategoryBO.getId());// 将其放到本地缓存中localCache.put(cacheKey, JSON.toJSONString(subjectCategoryBOS));} else {// 如果缓存中有则将其序列化然后返回subjectCategoryBOS JSON.parseArray(content, SubjectCategoryBO.class);}return subjectCategoryBOS;
}2.测试 3.本地缓存工具类
1.ListLocalCacheUtil.java
package com.sunxiansheng.subject.domain.util;import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;/*** Description: List本地缓存工具类guava* Author sun* Create 2024/6/16 17:20* Version 1.0*/
Component
public class ListLocalCacheUtilV {// 使用谷歌提供的本地缓存value的类型是String存什么数据直接序列化即可private CacheString, String localCache CacheBuilder.newBuilder().maximumSize(5000).expireAfterWrite(10, TimeUnit.SECONDS).build();/*** 根据key来获取本地缓存中的value如果有就直接获取如果没有就从db中查询* param cacheKey 构建的本地缓存的key* param clazz 要返回的类型* param supplier 函数式接口没有参数返回值为DB查询结果* return*/public ListV getResult(String cacheKey, ClassV clazz, SupplierListV supplier) {// 要返回的listListV resultList new LinkedList();// 根据key获取本地缓存的内容String content localCache.getIfPresent(cacheKey);if (StringUtils.isNotBlank(content)) {// 如果缓存中有就将其序列化resultList JSON.parseArray(content, clazz);} else {// 缓存中没有ListV作为返回值交给数据库中查具体逻辑交给函数式接口resultList supplier.get();if (!CollectionUtils.isEmpty(resultList)) {// 如果从数据库查出来的不是空就序列化然后放到缓存中localCache.put(cacheKey, JSON.toJSONString(resultList));}}return resultList;}}2.使用方式
1.依赖注入 2.使用
/*** 注意这里使用本地缓存的原因是分类和标签的改动不大* param subjectCategoryBO* return*/
Override
public ListSubjectCategoryBO queryCategoryAndLabel(SubjectCategoryBO subjectCategoryBO) {// 要查询的分类idLong categoryId subjectCategoryBO.getId();// 构建一个本地缓存的key结果是根据id查询所以key也根据这个构建String cacheKey categoryAndLabel. categoryId;// 使用本地缓存工具类本地缓存中获取数据如果没有再从数据库中获取然后将其放到本地缓存中ListSubjectCategoryBO subjectCategoryBOS listLocalCacheUtil.getResult(cacheKey, SubjectCategoryBO.class,() - {// 函数式接口参数空返回值ListSubjectCategoryBO对象逻辑从db中查ListSubjectCategoryBO然后返回return getSubjectCategoryBOS(subjectCategoryBO.getId());});return subjectCategoryBOS;
}3.测试