最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录


前言

最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要是确实时间紧,倒排的,其实个人一直不认可倒排估工时法,但是现在发现不管是大公司、小故事都喜欢这样搞。无语!工时不应该是一个纯粹的根据经验 + 个人能力做了可行性的一个耗时预估值吗?怎么就爱打骨折哦,不应该考虑堆人么?

跑远了,今天主要是简单分享最新版本springBoot3.5.0 + SpringCloud2025.0.0 + nacos2.5.1的架构规划与搭建分享。


一、服务规划

这里也只是简单规划下,不是按照最小粒度规划的,应该说就是简单按照业务线 + 基础组件的思路规划的。毕竟是个小公司,任务时间紧急,又没有专业运维,所以就简单规划下,后期服务器都不知道能不能配置全。

  • commom:通用jar包,这里将一些常用的集成、工具类、异常处理、子服务拦截器放这里
  • gateway:网关就不解释了,这里唯一要说的就是它是一个使用webflux响应式 Web 框架,完全非阻塞,支持响应式流背压。就是因为无关用这个,子服务用mvc,所以网关里的gateway接入redis是单独的,整个就不引入common,因为common里的统一异常处理是基于web的,与webflux不兼容,引入会报错。
  • device:业务线的子服务,涉密,就不多说了
  • admin:业务线的子服务,主要是后端管理的接口
  • third:业务线的子服务,用于处理第三方服务
  • exchange:业务线的子服务,涉密
  • code-helper:代码生成助手,我这里是mybatisplus基于自定义的模版生成基础代码,为了后面CRUD快,花不少时间集成、写自定义模版。
    其实,按道理应该还细化下common,分util、exception、mysqlplus、redis等等,但是看看兄弟们,就这么点人力,有这个基础微服务就行了。

二、架构核心

1.cloud的pom

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.rs.gov</groupId>
    <artifactId>gov-cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>gov-cloud</name>
    <description>gov-cloud</description>

    <modules>

        <module>gov-common</module>
        <module>gov-gateway</module>
        <module>gov-device</module>
        <module>gov-admin</module>
        <module>gov-third</module>
        <module>gov-exchange</module>

        <module>gov-code-helper</module>

    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
    </parent>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>

        <spring-boot.version>3.5.0</spring-boot.version>
        <spring-cloud.version>2025.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>


        <gov.common.version>0.0.1</gov.common.version>

        <mybatisPlus.version>3.5.7</mybatisPlus.version>
        <hutool.version>5.8.32</hutool.version>
        <redisson.version>3.36.0</redisson.version>
        <commons-io.version>2.19.0</commons-io.version>

        <netty.macSupport.version>4.1.121.Final</netty.macSupport.version>

        <skipTests>true</skipTests>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>






        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <!-- Maven Compiler -->
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.14.0</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                    </configuration>
                </plugin>

                <!-- Spring Boot Plugin -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

2.gateway的异常handler

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rs.gov.govgateway.entity.RestResponse;
import lombok.SneakyThrows;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author zwmac
 */
@Component
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @SneakyThrows
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        RestResponse<?> restResponse = RestResponse.build(500, ex.getMessage());

        DataBuffer buffer = response.bufferFactory()
                .wrap(objectMapper.writeValueAsBytes(restResponse));

        return response.writeWith(Mono.just(buffer));
    }
}

3.gateway的filter

java 复制代码
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.rs.gov.govgateway.constant.CommonConstant;
import com.rs.gov.govgateway.constant.HeaderConstant;
import com.rs.gov.govgateway.entity.RestResponse;
import com.rs.gov.govgateway.entity.Tenant;
import com.rs.gov.govgateway.entity.UserAccount;
import com.rs.gov.govgateway.entity.UserInfo;
import com.rs.gov.govgateway.props.WhiteListProp;
import com.rs.gov.govgateway.service.redis.RedisService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 网关拦截
 *
 * @author zwmac
 */
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GatewayFilter implements GlobalFilter, Ordered {

    @Value("${server.servlet.context-path:/gov-gateway}")
    private String contextPath;

    private final RedisService redisService;
    private final WhiteListProp whiteListProp;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String url = request.getURI().getPath();
        log.info("接收到请求:{}", url);
        // 跨域放行
        if (request.getMethod() == HttpMethod.OPTIONS) {
            response.setStatusCode(HttpStatus.OK);
            return Mono.empty();
        }
        // 授权
        ServerHttpRequest mutatedRequest = this.auth(exchange);
        if (mutatedRequest == null) {
            return this.responseBody(exchange, 401, "请先登录");
        }

        // 用新的 request 构造新的 exchange 再放行
        ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
        return chain.filter(mutatedExchange);
    }

    /**
     * 认证
     */
    private ServerHttpRequest auth(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        String path = request.getURI().getPath();

        if (whiteListProp.getEnable() && CollectionUtils.isNotEmpty(whiteListProp.getUrls())) {
            boolean whiteMatch = whiteListProp.getUrls().stream().anyMatch(url -> antPathMatcher.match(url.replace(contextPath, ""), path.replace(contextPath, "")));
            if (whiteMatch) {
                return request;
            }
        }

        String token = this.getToken(request);
        if (StringUtils.isEmpty(token)) {
            return null;
        }

        Object userObj = redisService.get(CommonConstant.PREFIX_USER_TOKEN + token);
        if (userObj == null) {
            return null;
        }

        UserInfo userInfo = JSONUtil.toBean(userObj.toString(), UserInfo.class);
        UserAccount accountInfo = userInfo.getAccountInfo();
        Tenant currentTenant = userInfo.getCurrentTenant();
        if (currentTenant != null && currentTenant.getId() != null) {
            accountInfo.setCurrentTenantId(currentTenant.getId());
        }

        return request.mutate()
                .header(HeaderConstant.USERID, accountInfo.getUserId())
                .header(HeaderConstant.USERNAME, URLEncoder.encode(accountInfo.getSysUserName(), StandardCharsets.UTF_8))
                .header(HeaderConstant.USER_ACCOUNT, URLEncoder.encode(accountInfo.getLoginName(), StandardCharsets.UTF_8))
                .header(HeaderConstant.TENANT_ID, accountInfo.getCurrentTenantId())
                .build();
    }


    /**
     * 获取token
     */
    public String getToken(ServerHttpRequest request) {
        HttpCookie tokenCookie = request.getCookies().getFirst(CommonConstant.X_ACCESS_TOKEN);
        if (tokenCookie == null) {
            String token = request.getHeaders().getFirst(CommonConstant.X_ACCESS_TOKEN);
            if (StrUtil.isNotEmpty(token)) {
                return token;
            }
            return request.getQueryParams().getFirst("token");
        }
        return tokenCookie.getValue();
    }

    /**
     * 设置响应体
     **/
    public Mono<Void> responseBody(ServerWebExchange exchange, Integer code, String msg) {
        String message = JSONUtil.parse(RestResponse.build(code, msg)).toString();
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        return this.responseHeader(exchange).getResponse()
                .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(bytes)));
    }

    /**
     * 设置响应体的请求头
     */
    public ServerWebExchange responseHeader(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
        return exchange.mutate().response(response).build();
    }

    @Override
    public int getOrder() {
        return -100;
    }

}

4、admin的pom

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.rs.gov</groupId>
        <artifactId>gov-cloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>gov-admin</artifactId>
    <name>gov-admin</name>
    <description>gov-admin</description>

    <dependencies>
        <!-- 通用jar -->
        <dependency>
            <groupId>com.rs.gov</groupId>
            <artifactId>gov-common</artifactId>
            <version>${gov.common.version}</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Nacos 注册与发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- Nacos 拉取配置依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- Feign 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>


        <!-- Spring Boot Actuator(可选) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

5、admin的登录核心

java 复制代码
//生存token,直接用户名+token的secret生成
        String token = DigestUtil.md5Hex(userName + tokenSecret).toLowerCase();

        UserInfo userInfo = new UserInfo();
        UserAccount userAccount = new UserAccount();
        BeanUtil.copyProperties(sysUser, userAccount);
        userAccount.setLoginName(userName);
        userInfo.setAccountInfo(userAccount);
        userInfo.setToken(token);

        String userInfoJsonStr = JSONUtil.toJsonStr(userInfo);
        redisService.save(CommonConstant.PREFIX_USER_TOKEN + token, userInfoJsonStr, tokenExpire);

        return RestResponse.success(userInfoJsonStr);

登录前面的校验密码、查库就不说了,这里的核心是要在redis缓存,返回token给前端,前端所有接口请求都在在请求头里放token。

三、code-helper分享

这个是代码生成的,基于最新的mybatis-plus-generator3.5.12,这个倒是可以完全分享,是不是应该单写一篇?请允许我这么坏,有需要的请移步到下一篇。


总结

好了,就写到这里,希望能帮到大家。现在虽然部门人少,但是团队的技术氛围不错,人少归人少,但是我们语种可全乎了,有.net、C++、QT、WPF、前端、java后端。公司个人都可承接软、硬、软硬组合项目哦,我们就是这么自信,请允许我小小的骄傲,因为我有一个好平台的依靠!

行业前景也挺好,如果现在从事的能打开市场,按照老板的愿景,3年后我们就年产过亿,可以上市了。加油!!!!

相关推荐
元気いっぱいの猫1 小时前
Spring Cloud 微服务架构实战指南 -- SpringCLoud概述
spring cloud·微服务·架构
保持学习ing3 小时前
SpringBoot前后台交互 -- 登录功能实现(拦截器+异常捕获器)
java·spring boot·后端·ssm·交互·拦截器·异常捕获器
七七&5563 小时前
【Java开发日记】基于 Spring Cloud 的微服务架构分析
java·spring cloud·架构
猕员桃3 小时前
《Spring Boot 微服务架构下的高并发活动系统设计与实践》
spring boot·微服务·架构
加什么瓦4 小时前
SpringCloud——微服务
spring·spring cloud·微服务
十年老菜鸟4 小时前
spring boot源码和lib分开打包
spring boot·后端·maven
白宇横流学长4 小时前
基于SpringBoot实现的课程答疑系统设计与实现【源码+文档】
java·spring boot·后端
pitt19975 小时前
AI 大模型统一集成|Spring AI + DeepSeek 实战接入指南
微服务·大模型api·springai·deepseek
爱编程的张同学6 小时前
Spring Cloud Alibaba Seata安装+微服务实战
分布式·spring cloud·微服务
佐斌6 小时前
基于 umi3 升级到 umi4
react.js·微服务