最新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年后我们就年产过亿,可以上市了。加油!!!!

相关推荐
smileNicky4 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端
SirLancelot16 小时前
K8s-kubernetes(二)资源限制-详细介绍
微服务·云原生·容器·kubernetes·k8s·devops·kubelet
柏油7 小时前
Spring @TransactionalEventListener 解读
spring boot·后端·spring
小小工匠9 小时前
Maven - Spring Boot 项目打包本地 jar 的 3 种方法
spring boot·maven·jar·system scope
板板正10 小时前
Spring Boot 整合MongoDB
spring boot·后端·mongodb
泉城老铁11 小时前
在高并发场景下,如何优化线程池参数配置
spring boot·后端·架构
泉城老铁11 小时前
Spring Boot中实现多线程6种方式,提高架构性能
spring boot·后端·spring cloud
hrrrrb12 小时前
【Java Web 快速入门】九、事务管理
java·spring boot·后端
为什么要内卷,摆烂不香吗14 小时前
Docker容器技术全面解析(一):入门
docker·微服务·容器
布朗克16814 小时前
Spring Boot项目通过RestTemplate调用三方接口详细教程
java·spring boot·后端·resttemplate