SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——登录,网关统一鉴权

Vue和FastAPI实现前后端分离 一个是它的异步处理性能比较好,二是它可以类似java swagger的API交互文档,这个对应前后端分离多人开发非常友好。之前总结过Vue和Flask实现前后端分离,这次同样借个小项目实践、并总结下Vue和FastAPI实现前后端分离。 阅读详情
SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.youkuaiyun.com/grd_java/article/details/144986730
SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理https://blog.youkuaiyun.com/grd_java/article/details/145013189

一、登录功能实现

1. 登录逻辑

根据我们的架构,必须确保redis和nacos的正常启动


在这里插入图片描述

只需额外添加Controller即可,登录由sa-token实现,其余相关业务前面讲rbac实现时,已经实现了


在这里插入图片描述

package com.yd_oa_java_cloud.security.controller;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import com.yd_oa_java_cloud.security.entity.SysRole;
import com.yd_oa_java_cloud.security.entity.SysUser;
import com.yd_oa_java_cloud.security.service.ISysUserRoleService;
import com.yd_oa_java_cloud.security.service.ISysUserService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :
 */
@RestController
@Log4j2
public class LoginController {
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private ISysUserRoleService sysUserRoleService;
    @RequestMapping("login")
    public YdOaResult doLogin(String username, String password) {
        // 此处仅做模拟登录,真实环境应该查询数据进行登录
        if (username==null || username.isEmpty()) return YdOaResult.error().setMsg("请输入用户名");
//            SaHolder.getRequest().getParam("code");//获取验证码
        try {
            SysUser user = sysUserService.getByUsernameOne(username);
            if (user == null) return YdOaResult.ok().setMsg("用户名不存在!");
            if(user.getUsername().equals(username) && BCrypt.checkpw(password,user.getPassword())) {
                StpUtil.login(user.getId());//登录
                SaTokenInfo tokenInfo = StpUtil.getTokenInfo();//获取token
                StpUtil.getSession().set("user",user);//实体类对象存入session
                return YdOaResult.ok("登录成功").set("tokenInfo",tokenInfo);
            }
            return YdOaResult.error("登录失败,请检查用户名和密码!");
        } catch (Exception e) {
            log.error("登录时出错");
            return YdOaResult.error().setCode(ResponseCodeEnum.CODE_503.getCode()).setMsg("登录出错!");
        }
    }
    @RequestMapping("LoginUserInfo")
    public YdOaResult loginUserInfo(){
        String uid = StpUtil.getLoginId().toString();//获取登录用户id
        SysUser user = (SysUser)StpUtil.getSession().get("user");//获取当前登录用户信息
        if (user.getRoles()==null) {//如果不包含登录用户的角色信息
            List<SysRole> rolesByUserId = sysUserRoleService.getRolesByUserId(uid);//查询角色
            user.setRoles(rolesByUserId);//添加角色
        }
        return YdOaResult.ok().set("id",uid)
                .set("userInfo",user);
    }
    @RequestMapping("isLogin")
    public YdOaResult isLogin() {
        return YdOaResult.ok("是否登录:").set("isLogin",StpUtil.isLogin());
    }
    // 查询 Token 信息  ---- http://localhost:8081/acc/tokenInfo
    @RequestMapping("tokenInfo")
    public YdOaResult tokenInfo() {
        return YdOaResult.ok().set("tokenInfo",StpUtil.getTokenInfo());
    }
    // 测试注销  ---- http://localhost:8081/acc/logout
    @RequestMapping("logout")
    public YdOaResult logout() {
        StpUtil.logout();
        return YdOaResult.ok("退出登录成功");
    }
}

2. 监听器和全局异常

全局异常就是一个mvc拦截器,具体代码不写了,大家可以根据自身业务情况,自由发挥


在这里插入图片描述

package com.yd_oa_java_cloud.security.handler;

import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    // 全局异常拦截
    @ExceptionHandler
    public SaResult handlerException(Exception e) {
        e.printStackTrace();
        return SaResult.error(e.getMessage());
    }
}

SaTokenListener就是一个AOP切面,监听器会在登录相关操作进行动态代理


在这里插入图片描述

package com.yd_oa_java_cloud.security.handler;

import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import org.springframework.stereotype.Component;
/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description : 自定义侦听器的实现
 */
@Component
public class MySaTokenListener implements SaTokenListener {

    /** 每次登录时触发 */
    @Override
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
        System.out.println("---------- 自定义侦听器实现 doLogin");
    }

    /** 每次注销时触发 */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doLogout");
    }

    /** 每次被踢下线时触发 */
    @Override
    public void doKickout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doKickout");
    }

    /** 每次被顶下线时触发 */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doReplaced");
    }

    /** 每次被封禁时触发 */
    @Override
    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
        System.out.println("---------- 自定义侦听器实现 doDisable");
    }

    /** 每次被解封时触发 */
    @Override
    public void doUntieDisable(String loginType, Object loginId, String service) {
        System.out.println("---------- 自定义侦听器实现 doUntieDisable");
    }

    /** 每次二级认证时触发 */
    @Override
    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
        System.out.println("---------- 自定义侦听器实现 doOpenSafe");
    }

    /** 每次退出二级认证时触发 */
    @Override
    public void doCloseSafe(String loginType, String tokenValue, String service) {
        System.out.println("---------- 自定义侦听器实现 doCloseSafe");
    }

    /** 每次创建Session时触发 */
    @Override
    public void doCreateSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doCreateSession");
    }

    /** 每次注销Session时触发 */
    @Override
    public void doLogoutSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doLogoutSession");
    }

    /** 每次Token续期时触发 */
    @Override
    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
        System.out.println("---------- 自定义侦听器实现 doRenewTimeout");
    }
}

二、网关鉴权

1. sa-token过滤器

网关和普通微服务代码几乎一样,只不过网关这里new的是SaReactorFilter这个类


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.filter;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
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 com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :
 * [Sa-Token 权限认证] 配置类
 */
@Configuration
@Slf4j
public class SaTokenConfigure {

    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        log.info("SaTokenConfigure:权限路径鉴权");
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 开放地址
                .addExclude("/security/login")
                //
//                .addExclude("/security/**")
                // 鉴权方法:每次访问进入a
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
//                    SaRouter.match("/**", "/security/login", r -> StpUtil.checkLogin());

                    // 权限认证 -- 不同模块, 校验不同权限
                    SaRouter.match("/security/sysUser/**", r -> StpUtil.checkLogin());
                    SaRouter.match("/security/sysUserRole/**", r -> StpUtil.checkPermission("security"));
                    SaRouter.match("/security/sysMenu/**", r -> StpUtil.checkLogin());
                    // 更多匹配 ...  */
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    // 打印堆栈,以供调试
                    e.printStackTrace();
                    if (e instanceof NotLoginException){
                        NotLoginException nle = (NotLoginException) e;
                        // 判断场景值,定制化异常信息
                        Integer code = 0;
                        String message = "";
                        if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
                            code = ResponseCodeEnum.CODE_500.getCode();
                            message = "未能读取到有效 token";
                        }
                        else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
                            code = ResponseCodeEnum.CODE_50008.getCode();
                            message = "token 无效";
                        }
                        else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
                            code = ResponseCodeEnum.CODE_50014.getCode();
                            message = "token 已过期";
                        }
                        else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
                            code = ResponseCodeEnum.CODE_50012.getCode();
                            message = "token 已被顶下线";
                        }
                        else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
                            code = ResponseCodeEnum.CODE_50012.getCode();
                            message = "token 已被踢下线";
                        }
                        else if(nle.getType().equals(NotLoginException.TOKEN_FREEZE)) {
                            code = ResponseCodeEnum.CODE_500.getCode();
                            message = "token 已被冻结";
                        }
                        else if(nle.getType().equals(NotLoginException.NO_PREFIX)) {
                            code = ResponseCodeEnum.CODE_50008.getCode();
                            message = "未按照指定前缀提交 token";
                        }
                        else {
                            code = ResponseCodeEnum.CODE_401.getCode();
                            message = "当前会话未登录";
                        }
                        // 返回给前端
                        return YdOaResult.code(code).setMsg(message);
                    }else{
                        return SaResult.error().setMsg(e.getMessage());
                    }

                })
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {

                    // 获得客户端domain
                    SaRequest request = SaHolder.getRequest();
                    String origin = request.getHeader("Origin");
                    if (origin == null) {
                        origin = request.getHeader("Referer");
                    }

                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许第三方 Cookie
                            .setHeader("Access-Control-Allow-Credentials", "true")
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", origin)
                            // 允许所有请求方式
//                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, DELETE")
                            .setHeader("Access-Control-Allow-Methods", "*")
                            // 允许的header参数
//                            .setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken,authorization")
                            .setHeader("Access-Control-Allow-Headers", "*")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                    ;

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                })
                ;
    }
}

2. 内部服务外网隔离

我们有了网关,要通过网关统一鉴权,同时,不可以绕过网关直接访问子级服务。


Sa-Token中提供了Same-Token机制,只需要配置全局拦截器即可。每次网关转发请求都添加same-token


还记得我们配置gateway网关时,配置了全局过滤器,但是没有写逻辑吗,这里就是填坑的时刻


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.filter;

import cn.dev33.satoken.same.SaSameUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :  openFeign 调用security微服务相关接口
 * gateway 全局过滤器,可以调用spring Security微服务进行鉴权
 * implements GlobalFilter 实现GlobalFilter接口,实现全局过滤器。可以有多个
 * implements Ordered 实现过滤器的先后顺序,如果有多个过滤器,通过此接口规定每个过滤器的先后顺序
 */
@Component//组件组成,交由SpringIOC容器管理
@Slf4j //日志
public class GateWayGlobalRequestFilter implements GlobalFilter , Ordered {
    //全局过滤器,为请求添加 Same-Token
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String rawPath = exchange.getRequest().getURI().getRawPath();//获取请求路径
        log.info("GateWayGlobalRequestFilter:"+rawPath);
        ServerHttpRequest newRequest = exchange
                .getRequest()
                .mutate()
                // 为请求追加 Same-Token 参数
                .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken())
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
        //过滤完成,可以向下传递
//        return chain.filter(exchange);
    }
    //定义此过滤器为所有过滤器的第0层过滤器
    @Override
    public int getOrder() {
        return 0;
    }
}

同时子级服务(哪个需要实现不允许外部直接访问的功能就配置哪个)也要添加校验same-token的机制,其实就是一个webMVC过滤器


在这里插入图片描述

package com.yd_oa_java_cloud.security.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :  Sa-Token 权限认证 配置类
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    //SaTokenConfigure 配置注解拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
    }
    // 注册 Sa-Token 全局过滤器
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                .addInclude("/**")
//                .addExclude("/favicon.ico")
                .setAuth(obj -> {
                    // 校验 Same-Token 身份凭证     —— 以下两句代码可简化为:SaSameUtil.checkCurrentRequestToken();
                    String token = SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN);
                    SaSameUtil.checkToken(token);
                })
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                ;
    }
}

3. 权限与角色

我们过滤请求时,需要获取当前用户的角色和权限,此时需要调用子级服务security中内容,就需要feign组件的登场了

3.1 feign组件依赖和配置

1. 引入依赖


在这里插入图片描述

<!--        远程调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
<!--        远程调用连接池-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

yml文件中需要配置启用连接池,否则很慢的(推荐配置到nacos配置中心)


feign:
    okhttp:
        enabled: true #feign组件

3.2 配置服务间内部调用鉴权

前面搞了内部服务外网隔离,此时子服务每次有人请求都要校验same-token


而远程调用可不走全局过滤器,也就是我们上面对于网关的操作是没办法在远程调用的时候为我们加上same-token的

很简单,构建一个Feign的拦截器就可以了


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.filter;

import cn.dev33.satoken.same.SaSameUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

/**
 * _*_ coding : utf-8 _*_
 *
 * @Time : 2025/1/16 0016 14:38
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : FeignInterceptor
 * @Project : yd_oa_java_cloud
 * @Description : feign拦截器, 在feign请求发出之前,加入一些操作
 */
@Component
public class FeignInterceptor implements RequestInterceptor {
    // 为 Feign 的 RCP调用 添加请求头Same-Token
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());

        // 如果希望被调用方有会话状态,此处就还需要将 satoken 添加到请求头中
        // requestTemplate.header(StpUtil.getTokenName(), StpUtil.getTokenValue());
    }
}

3.3 启动类配置feign的启用

在这里插入图片描述


添加两个注解即可


@EnableFeignClients
@EnableDiscoveryClient

3.4 配置feign远程调用client

注意配置上拦截器哦

在这里插入图片描述

package com.yd_oa_java_cloud.gateway.feign.security;

import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import com.yd_oa_java_cloud.gateway.filter.FeignInterceptor;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :  openFeign 调用security微服务相关接口
 */
@FeignClient(
        name = "yd-oa-java-security",// 服务名称
        configuration = FeignInterceptor.class        // 请求拦截器 (关键代码)
        //fallbackFactory = SpCfgInterfaceFallback.class    // 服务降级处理
)
public interface SecuritySysUserRoleClient {
    @GetMapping("sysUserRole/getById/{uid}")
    public YdOaResult getById(@PathVariable String uid);
    //根据用户id获取菜单
    @GetMapping("sysMenu/getMenusByUserId/{id}")
    public YdOaResult getMenusByUserId(@PathVariable String id);
}

3.5 手动注入HttpMessageConverters

Spring Cloud Gateway是基于WebFlux的,是ReactiveWeb,所以异步和并发时,HttpMessageConverters不会自动注入

记住在gateway这样的响应式网关,是无法直接使用同步请求的,要求用异步进行。强行用同步会直接报错,虽然可以配置为强行使用同步,但是为什么我们不用异步遵循响应式编程呢?


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.config;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.stream.Collectors;

/**
 * _*_ coding : utf-8 _*_
 *
 * @Time : 2025/1/15 0015 10:13
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : FeignConfig
 * @Project : yd_oa_java_cloud
 * @Description : gateway配置类,Spring Cloud Gateway是基于WebFlux的,是ReactiveWeb,所以异步和并发时,HttpMessageConverters不会自动注入。
 */
@Configuration
public class FeignConfig {
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }
}

3.6 实现鉴权接口

这里就正式到了,feign远程调用,注意上面说到的,不可以同步远程调用,要用异步


可以使用CompletableFuture类进行异步任务编排,然后通过completableFuture.get()方法执行编排好的任务,并等待它响应成功


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.service;

import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import com.yd_oa_java_cloud.base.exception.BusinessException;
import com.yd_oa_java_cloud.gateway.feign.security.SecuritySysUserRoleClient;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description : 配置当前登录用户的角色和权限列表
 */
@Component
@Log4j2
public class StpInterfaceImpl implements StpInterface {
    @Autowired
    @Lazy
    private SecuritySysUserRoleClient securitySysUserRoleClient;
    //获取权限列表
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        String id = loginId.toString();
        //并发异步任务编排
        CompletableFuture<YdOaResult> completableFuture = CompletableFuture.supplyAsync(() -> {
            return securitySysUserRoleClient.getMenusByUserId(id);
        });
        //执行编排
        try {
            YdOaResult ydOaResult = completableFuture.get();

            if (!Objects.equals(ydOaResult.getCode(), ResponseCodeEnum.CODE_20000.getCode()))
                throw new BusinessException(ydOaResult.getCode(),ydOaResult.getMsg());

            List data = (List) ydOaResult.get("menus");
            ArrayList<String> list = new ArrayList<>();
            for (Object object:data) {
                HashMap<String, String> map = (HashMap<String, String>) object;
                String name = map.get("permission");
                list.add(name);
            }
            // 返回此 loginId 拥有的权限列表
            return list;
        } catch (InterruptedException e) {
            log.error("getPermissionList并发出错!!————",e);
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            log.error("getPermissionList并发出错!!————",e);
            throw new RuntimeException(e);
        } catch (BusinessException e){
            log.error("getPermissionList并发出错!!————",e.getCode(),"————",e.getMessage());
            throw e;
        }

    }
    //获取角色列表
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        String uid = StpUtil.getLoginId().toString();
        //并发异步任务编排
        CompletableFuture<YdOaResult> completableFuture = CompletableFuture.supplyAsync(() -> {
            return securitySysUserRoleClient.getById(uid);
        });
        //执行编排
        try {
            YdOaResult ydOaResult = completableFuture.get();

            if (!Objects.equals(ydOaResult.getCode(), ResponseCodeEnum.CODE_20000.getCode()))
                throw new BusinessException(ydOaResult.getCode(),ydOaResult.getMsg());

            List data = (List) ydOaResult.getData();
            ArrayList<String> list = new ArrayList<>();
            for (Object object:data) {
                HashMap<String, String> map = (HashMap<String, String>) object;
                String name = map.get("name");
                list.add(name);
            }
            // 返回此 loginId 拥有的角色列表
            return list;
        } catch (InterruptedException e) {
            log.error("getRoleList并发出错!!————",e);
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            log.error("getRoleList并发出错!!————",e);
            throw new RuntimeException(e);
        } catch (BusinessException e){
            log.error("getRoleList并发出错!!————",e.getCode(),"————",e.getMessage());
            throw e;
        }
    }

}

4. SaToken全局异常处理

常用的就没必要try-catch了


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.NotPermissionException;
import com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :  SaToken全局异常处理
 */
@RestControllerAdvice
public class SaTokenGlobalException {
    @ExceptionHandler(NotLoginException.class)
    public YdOaResult handlerException(NotLoginException e) {//未登录全局异常
        ResponseCodeEnum res = ResponseCodeEnum.CODE_401;
        return YdOaResult
                .code(res.getCode())
                .setMsg(res.getMsg());
    }

    @ExceptionHandler(NotRoleException.class)
    public YdOaResult handlerException(NotRoleException e) {//缺少角色
        ResponseCodeEnum role = ResponseCodeEnum.CODE_403Role;
        return YdOaResult
                .code(role.getCode())
                .setMsg(role.getMsg()+e.getRole());
    }

    @ExceptionHandler(NotPermissionException.class)
    public YdOaResult handlerException(NotPermissionException e) {//缺少权限
        ResponseCodeEnum permission = ResponseCodeEnum.CODE_403Permission;
        return YdOaResult
                .code(permission.getCode())
                .setMsg(permission.getMsg()+e.getPermission());
    }
}

5. 子级服务内部注解鉴权

多此一举!

除了网关统一鉴权外,如果你还想要子级服务内进行鉴权,也可以使用注解,但是需要再次实现鉴权接口


1. 保证配置了注解拦截器


在这里插入图片描述

前面的内部服务外网隔离中已经配置过了,这里不多赘述

2. 调用相关接口,获取当前登录用户的权限和角色列表,也可以在网关转发之前,例如添加same-token的时候,顺便将角色和权限列表加进去


在这里插入图片描述

package com.yd_oa_java_cloud.security.handler;

import cn.dev33.satoken.stp.StpInterface;
import com.yd_oa_java_cloud.base.exception.BusinessException;
import com.yd_oa_java_cloud.security.entity.SysMenu;
import com.yd_oa_java_cloud.security.entity.SysRole;
import com.yd_oa_java_cloud.security.service.ISysMenuService;
import com.yd_oa_java_cloud.security.service.ISysUserRoleService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description : 配置当前登录用户的角色和权限列表
 */
@Component
@Log4j2
public class StpInterfaceImpl implements StpInterface {
    @Autowired
    private ISysMenuService sysMenuService;
    @Autowired
    private ISysUserRoleService sysUserRoleService;
    //获取权限列表
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {

        try {
            ArrayList<String> list = new ArrayList<>();
            List<SysMenu> menusByLoginId = sysMenuService.getMenusByLoginId();
            for (SysMenu sysMenu:menusByLoginId) {
                list.add(sysMenu.getPermission());
            }
            // 返回此 loginId 拥有的权限列表
            return list;
        } catch (BusinessException e){
            log.error("security/getPermissionList!!————",e.getCode(),"————",e.getMessage());
            throw e;
        }

    }
    //获取角色列表
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {

        try {
            List<SysRole> rolesByUserId = sysUserRoleService.getRolesByUserId((String) loginId);

            ArrayList<String> list = new ArrayList<>();
            for (SysRole object:rolesByUserId) {
                list.add(object.getName());
            }
            log.info(list);
            // 返回此 loginId 拥有的角色列表
            return list;
        }  catch (BusinessException e){
            log.error("security/getRoleList!!————",e.getCode(),"————",e.getMessage());
            throw e;
        }
    }

}

3. 需要的接口上,配置注解鉴权即可


在这里插入图片描述

6. 跨越解决

网关统一转发,因此网关satoken鉴权前,先添加跨越响应头


在这里插入图片描述

package com.yd_oa_java_cloud.gateway.filter;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
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 com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.YdOaResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * _*_ coding : utf-8 _*_
 * @Time : 2025/1/14 星期二 10:30
 * @Author : Taylor Sinclair(殷志鹏)
 * @File : ${NAME}
 * @Project : ${PROJECT_NAME}
 * @Description :
 * [Sa-Token 权限认证] 配置类
 */
@Configuration
@Slf4j
public class SaTokenConfigure {

    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        log.info("SaTokenConfigure:权限路径鉴权");
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 开放地址
                .addExclude("/security/login")
                //
//                .addExclude("/security/**")
                // 鉴权方法:每次访问进入a
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
//                    SaRouter.match("/**", "/security/login", r -> StpUtil.checkLogin());

                    // 权限认证 -- 不同模块, 校验不同权限

                    SaRouter.match("/security/**", r -> StpUtil.checkPermission("security"));

                    // 更多匹配 ...  */
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    // 打印堆栈,以供调试
                    e.printStackTrace();
                    if (e instanceof NotLoginException){
                        NotLoginException nle = (NotLoginException) e;
                        // 判断场景值,定制化异常信息
                        Integer code = 0;
                        String message = "";
                        if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
                            code = ResponseCodeEnum.CODE_500.getCode();
                            message = "未能读取到有效 token";
                        }
                        else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
                            code = ResponseCodeEnum.CODE_50008.getCode();
                            message = "token 无效";
                        }
                        else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
                            code = ResponseCodeEnum.CODE_50014.getCode();
                            message = "token 已过期";
                        }
                        else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
                            code = ResponseCodeEnum.CODE_50012.getCode();
                            message = "token 已被顶下线";
                        }
                        else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
                            code = ResponseCodeEnum.CODE_50012.getCode();
                            message = "token 已被踢下线";
                        }
                        else if(nle.getType().equals(NotLoginException.TOKEN_FREEZE)) {
                            code = ResponseCodeEnum.CODE_500.getCode();
                            message = "token 已被冻结";
                        }
                        else if(nle.getType().equals(NotLoginException.NO_PREFIX)) {
                            code = ResponseCodeEnum.CODE_50008.getCode();
                            message = "未按照指定前缀提交 token";
                        }
                        else {
                            code = ResponseCodeEnum.CODE_401.getCode();
                            message = "当前会话未登录";
                        }
                        // 返回给前端
                        return YdOaResult.code(code).setMsg(message);
                    }else{
                        return SaResult.error().setMsg(e.getMessage());
                    }

                })
                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {

                    // 获得客户端domain
                    SaRequest request = SaHolder.getRequest();
                    String origin = request.getHeader("Origin");
                    if (origin == null) {
                        origin = request.getHeader("Referer");
                    }

                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许第三方 Cookie
                            .setHeader("Access-Control-Allow-Credentials", "true")
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", origin)
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken,authorization")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                    ;

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                })
                ;
    }
}

三、测试

1. 使用网关访问正常


在这里插入图片描述


2. 直接请求子级服务,会报没有same-token异常


在这里插入图片描述

3. 随便登录一个没有security模块权限的用户


在这里插入图片描述


此时访问security服务接口会报错没有权限


在这里插入图片描述

使用 FastAPI 和 Vue.js 实现前后端分离 前后端分离是现代 Web 开发的趋势。使用 FastAPI 和 Vue.js 可以构建一个高效、灵活且易于维护的 Web 应用。FastAPI 提供了高性能的后端服务,而 Vue.js 作为一种渐进式 JavaScript 框架,可以构建动态的前端界面。本文将详细介绍如何使用 FastAPI 和 Vue.js 实现前后端分离的项目,包括环境搭建、API 开发、前端页面构建以及数据交互。 阅读详情

相关推荐

AI辅助开发:FastAPI和VUE联合工作

AI辅助开发:FastAPI和VUE联合工作

天涯雨的博客 1215

FastAPI 教程、结合vue实现前后端分离

FastAPI 内部提供了一个 HTTPException,但是我们也可以自定义,但是注意:我们自定义完异常之后,还要定义一个 handler,将异常和 handler 绑定在一起,然后引发该异常的时候就会触发相应的 handler。""""""pass# 通过装饰器的方式,将 ASCIIException 和 ascii_exception_handler 绑定在一起"""当引发 ASCIIException 的时候,会触发 ascii_exception_handler 的执行。

freeking101的博客 1万+

从零构建全栈用户管理系统:FastAPI + Vue 3 + MySQL 实战指南

用户列表展示与关键词搜索(支持用户名和邮箱搜索)新增用户(包含用户名、年龄、邮箱信息)编辑现有用户信息删除用户(含确认机制,防止误操作)表单验证与错误处理加载状态与操作反馈本文详细介绍了基于 FastAPI + Vue 3 + MySQL 的用户管理系统实现过程,涵盖了后端 API 设计、数据库交互、前端状态管理、组件开发等核心知识点。如何使用 FastAPI 快速构建 RESTful API,结合 Pydantic 进行数据验证。

2303_77081844的博客 2495

基于python FastAPI + Vue的一套CMS开源源码模板

FastAPI-Vue是个人开发并使用的CURD模板之一,代码功能不难,主要是减少浪费时间在用户系统的开发。fastAPI的性能在Python中还算挺不错的,使用起来也很方便。github上也有其他fastAPI和vue组合的代码,个人觉得不是很符合我自己,然后就自己开发了一套,现开源共享出来。后端项目的目录结构借鉴了Django的目录结构。前端Vue代码使用了Ruoyi的Vue代码。大部分功能和若依相似,但是在原基础上删除了我个人认为用得很少的 部门 模块。

sinat_34149445的博客 1万+

程序自动化填写网页表单数据

如何让程序自动化填写网页表单数据,特别是涉及到批量数据情况时,可以减少人力。下面是涉及到的一些场景,都可以通过相关自动化程序实现。

Y 2426

2021年全国职业院校技能大赛高职组网络系统管理赛项

S1 Ruijie(config)#switch virtual domain 1 Ruijie(config-vs-domain)#switch 1 Ruijie(config-vs-domain)#switch 1 priority 150 Ruijie(config-vs-domain)#switch 1 description S1 Ruijie(config-vs-domain)#exit Ruijie(config)#vsl-port Ruijie(config-vsl-port)#po

bfq05234214的博客 1133

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新)

随着公司业务的增加,公司需要一个java+python人工智能相互配合架构,正常网站业务用java来做,而ai,例如电价预测等回归任务,以及大模型预测全网负荷,新能源出力等任务,使用python通过fastapi暴露接口来做,那么就需要springcloud架构注册发现。前端统一使用Vue进行效果的展示

殷志鹏的博客 2484

海思Hi3516CV610--SPI NAND Flash移植实战:从手册到驱动的完整适配

本文详细介绍了海思Hi3516CV610芯片上SPI NAND Flash的移植实战,包括从手册解读到驱动适配的完整流程。以GD5F2GM7UEYIGR Flash为例,重点讲解了关键参数确认、模式选择、时序配置以及U-Boot和Linux内核驱动的具体实现步骤,帮助开发者快速完成Flash移植工作。

weixin_29158413的博客 447

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理

【代码】SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理。

殷志鹏的博客 936

保姆级教程:用杰理701N可视化SDK搞定ANC降噪配置(从工具到代码全流程)

本文提供了一份详细的杰理701N可视化SDK配置ANC降噪功能的教程,涵盖从开发环境搭建到代码集成的全流程。通过图形化工具和代码示例,帮助开发者快速掌握ANC参数配置技巧,优化降噪效果,解决常见问题。

weixin_30616969的博客 450

达梦数据库JDBC连接池断开自动重连设置

一、场景 在网络状况不是非常良好,经常会出现暂时性的拥塞或者断开的情况,而且当我们重启数据库时也会发生类似的情况。所以需要配置中间件的连接池来实现连接测试以及自动重连,通过重新配置连接池,成功解决了这个问题。下面会给出一份数据源配置参数详单和一份推荐的数据源配置项。 二、连接池配置参数 1. initialSize:连接池启动时创建的初始化连接数量 2. maxActive:连接池中可同时连接的最大的连接数 3. maxIdle:连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负

lilv1989的博客 7235

SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Gateway网关

【代码】SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Gateway网关。

殷志鹏的博客 1204

SD卡烧写Linux kernel——SD卡分区,并烧写uboot,kernel,DTB及filesystem

Linux Ubuntu环境下对SD卡分区(烧写uboot,kernel,及filesystem) 介绍dd命令的使用,SD卡分区、格式化、复制文件等 fdisk命令

weixin_42408707的博客 5056

FastAPI+Vue敏捷开发框架

一款 Python 语言基于FastAPI、Vue、ElementUI、MySQL等框架精心打造的一款模块化、高性能、企业级的敏捷开发框架,本着简化开发、提升开发效率的初衷触发,框架自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传等一系列个性化、轻量级的组件,是一款真正意义上实现组件化开发的敏捷开发框架。

一米阳光的博客 1063

弱小目标检测跟踪算法研究(5) 基于顶帽变换(Top_hat)算法的红外弱小目标检测之背景抑制

基于顶帽变换(Top_hat)算法的红外弱小目标检测之背景抑制1. 前言2. 顶帽变换(Top_hat)算法3. Matlab仿真4. 小结 1. 前言 红外图像中的弱小目标,目标属性包涵“弱"和“小’’两个方面:“弱’’是指目标在红外波长上所表现出来的强度,具体反映到所拍摄的红外图像上,就是指目标的灰度值;而“小’’是指目标的尺寸大小,也就是前面所述的成像面积很小,反映到红外图像上就是指目标所占的像素数目很少。 SPIE国际光学工程学会(Societyof Photo一0ptical Instrument

sinat_34897952的博客 5293

【在 WSL2 GUI 中使用 pulseaudio 播放声音】

最近在搞一个Linux项目,由于WSL2中需要用到声音外放,无奈只能潜心研究。后来就使用了一种折中的方式替代,那就是pulseaudio。目前来讲WSL2对硬件外设的支持还是不太友好,关于USB的支持我之前在其他文章中已经论述过了,这里不讨论。1、Windows11别忘记打开声音2、可能有一定延迟,因为是通过网络传输的。3、效果的话只能见仁见智了,如果不能满足要求就安装一个实实在在的Ubuntu吧!

jiexijihe945的博客 4530

【免费下载】 推荐开源项目:Vue3-FastAPI —— 前后端一体化开发的新星

推荐开源项目:Vue3-FastAPI —— 前后端一体化开发的新星 是一个创新的开源项目,它结合了前端的 Vue.js 3 和后端的 FastAPI,为你提供了一站式的现代Web应用开发框架。该项目旨在简化开发流程,提高效率,并且让开发者能够更好地利用最新技术和工具。 项目简介 Vue3-FastAPI 提供了一个完整的前后端解决方案,包括基础模板、身份验证系统和数据库模型。前端部分基于 Vue...

gitblog_00087的博客 2648

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

部署前后端分离项目Vue+FastAPI

将vue.config.js文件中的publicPath: '/',改为publicPath: './' (多了一个点)生成的dist文件夹,点开,然后浏览器打开index.html文件,如果静态页面正常,说明打。2. 前端部署,用npm run build:prod(以package.json中的为准)7:前后端连接,是将后端写的跨域部分中加入前端地址。将生产环境的地址改为 -> 服务器地址+后端的端口号。6:地址栏输入:服务器地址+前端端口号。3:将打包到的前端dist上传到这里。

qq_44658163的博客 1674

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

FastAPI + Vue3 + NaiveUI 框架的开发技巧

在定义路由时,可以为每个路由设置对应的权限标识,然后在路由守卫中,根据用户当前的角色或者权限信息来判断是否允许访问该路由,如果用户没有权限访问,则可以重定向到无权访问页面或者登录页面等,从而保证系统的安全性,防止用户直接通过路由地址访问没有权限的页面。NaiveUI 是一个基于 Vue3 的 UI 组件库,提供了丰富的组件,如表格(用于展示用户列表、角色列表等)、表单(用于创建和编辑用户、角色等信息)、弹窗(用于显示编辑表单、确认删除对话框等)、按钮(触发各种操作,如查询、保存、删除等)等。

xuukai的博客 2081

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

后端“fastapi”+前端“vue3+ts+ElementPlus”上传文件到uploads目录

确保已安装好python3和fastapimail.py运行fastapi服务器使用浏览器访问。

airdot的专栏 1838

fastapi+vue实现前后端分离

这段代码是在 FastAPI 应用中添加了一个跨域资源共享(CORS)中间件。CORS 是一种机制,它允许网页从不同的源(域名、协议、端口)请求资源,通过配置合适的 CORS 设置,可以确保前端应用(运行在浏览器中的 JavaScript 代码)能够安全、正确地与后端 FastAPI 应用进行交互,即使它们位于不同的源。为了使前端 Vue 应用能够访问 FastAPI 服务,需要设置 CORS。使用 Axios 在 Vue 组件中连接到 FastAPI 提供的 API。(但我们的小程序中并没有用到)

2301_80089510的博客 2073

vue3 + fastapi 实现选择目录所有文件自定义上传到服务器

大家好,我是yma16,本文分享关于vue3 + fastapi 实现选择目录文件上传到服务器指定位置。 vue3系列相关文章: 前端vue2、vue3去掉url路由“ # ”号——nginx配置 csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板 认识vite_vue3 初始化项目到打包 python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示 python系列文章: python爬虫_基本数据类型 python爬虫_函数

编程知识分享,主打前端 2万+

FastAPI + Vue 实现 OAuth2 的 jwt token 登录验证

后端使用 FastAPI,前端使用 Vue 来完成登录过程的用 jwt token 实现登录验证功能。 一、后端 FastAPI 1.1 设计用户表 本文采用的是 MySQL 数据库。 首先连接 MySQL 数据库,关于 MySQL 数据库的连接可参见另外一篇文章:FastAPI 连接 MySQL 用户的数据库表如下: class User(Base): __tablename__ = 'users' # 数据库表名 username = Column(String(255), prim

VLyb 7945

FastAPI+VUE项目部署在CentOS 7(一文搞定)

部署FastAPI+VUE项目的全过程

玖的博客 3297

fastapi+vue实现按钮级别的权限控制

1.1 自定义指令1.2 注册自定义指令1.3 在组件中还用自定义指令。

qq_44906497的博客 709

【免费下载】 推荐开源项目:Vue-FastAPI-Admin - 前后端分离的强大管理框架

是一个基于 Vue.js 和 FastAPI 的现代化、高性能的后台管理系统模板。它提供了丰富的预设组件和功能,旨在帮助开发者快速搭建稳定且高效的后台应用,减少重复工作,提高开发效率。 ## 技术分析 ### 前端:Vue.js Vue.js 是一种轻量级但功能强大的前端 JavaScript 框架,以其简单易学、可复用性高和易于维护的特点著称。Vue-FastAPI-Admin 利用了 V...

gitblog_00055的博客 2203
上一篇: SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——Sa-Token实现Security权限管理
下一篇: SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——SpringCloud后端——权限管理rbac接口实现
ydenergy_殷志鹏
博客等级 码龄7年 5885粉丝 543原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值