Vue修炼手册 Day04

该博客介绍了在Vue应用中如何完善退出功能,通过自定义Shiro过滤器解决登出请求的跨域问题,并展示了在Shiro配置类中使用自定义过滤器的实现。同时,详细讲解了如何根据用户权限动态获取菜单,包括后台接口设计和服务实现,以及前端页面如何动态生成菜单。涉及的技术包括Spring Shiro、Redis和Vue.js。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


继续完善Day03的Demo Day03

1. 完善退出功能

之前登出更能写完之后,有一个登出方法跨域问题,因为前后分离,前后端不共用一个Session,而Shiro默认其他请求路径需要认证之后才可以访问,故Shiro拦截器直接将登出请求拦截。

那为什么不能直接放行呢?答案肯定是不行的,因为这里如果我们直接放行登出请求的Url是不妥的。

所以我们可以为登出写一个过滤器。

1.1 登出过滤器

@Component
public class MyFilter extends FormAuthenticationFilter {

    @Resource
    private  RedisTemplate<String,Object> redisTemplate;
    //赋值一个静态的redisTemplate
    public static RedisTemplate redis;

    @PostConstruct //构造时赋值
    public void redisTemplate(){
        redis=this.redisTemplate;

    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //对OPTION请求方式放行 其他的请求方式都拦截。
        HttpServletRequest req= (HttpServletRequest) request;//多态 向下转型
        HttpServletResponse resp= (HttpServletResponse) response;
        String method = req.getMethod();
        System.out.println(method);
        if(RequestMethod.OPTIONS.name().equals(method)) {
            return true;
        }
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String token = req.getHeader("token");
        User o = (User)redis.opsForValue().get(token);
        if (StringUtils.isBlank(token) || o == null){
            resp.setCharacterEncoding("UTF-8");
            CommonResult result = new CommonResult(5000, "请先登录", null);
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(resp.getWriter(),result);
            return false;
        }
        return true;
    }
}

1.2 在Shiro配置类中使用自定义过滤器

        HashMap<String, Filter> filters = new HashMap<>();
        filters.put("authc",new MyFilter());
        shiroFilterFactoryBean.setFilters(filters);

2. 根据权限动态获取菜单

2.1 后台接口

Controller:

@RestController
@CrossOrigin
@Api(tags = "权限菜单Api")
@RequestMapping("/permission")
public class PermissionController {
    @Autowired
    private PermissionService permissionService;

    @GetMapping("getAllPermissonByUserId")
    public CommonResult getAllPermissonByUserId(){
        return permissionService.findAllPermissonByUserId();
    }
}

Service:
采用递归动态获取菜单及菜单下的子菜单

@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionDao, Permission> implements PermissionService {

    @Resource
    private PermissionDao permissionDao;
    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    public CommonResult findAllPermissonByUserId() {
        //获取请求头的token
        String key = WebUtil.getRequest().getHeader("token");
        //获取redis中的用户信息
        User user = (User)redisTemplate.opsForValue().get(key);
        //查询所有的菜单项
        List<Permission> list = permissionDao.selectByUserId(user.getId());
        //得到一级菜单
        ArrayList<Permission> firstMenu = new ArrayList<>();

        for (Permission permission : list) {
            if (permission.getPid().equals("1")){
                firstMenu.add(permission);
            }
        }

        //为一级菜单添加子菜单
        for (Permission menu : firstMenu) {
            menu.setChildren(findChildren(list,menu.getId()));
        }
        return new CommonResult(2000,"查询菜单成功",firstMenu);
    }

    private List<Permission> findChildren(List<Permission> permissions,String pid){
        ArrayList<Permission> children = new ArrayList<>();
        //如果菜单列表中有pid和传进来的pid相同的 则为对应的子菜单
        for (Permission permission : permissions) {
            if (permission.getPid().equals(pid)){
                children.add(permission);
            }
        }
        for (Permission child : children) {
            child.setChildren(findChildren(permissions,child.getId()));
        }
        return children;
    }
}

entity:
解决LocalDateTime无法从Redis反序列化的问题
①引入依赖

      <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

②实体类LocalDateTime属性上加入注解

    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("acl_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "用户id")
      private String id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "昵称")
    private String nickName;

    @ApiModelProperty(value = "用户头像")
    private String salt;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime gmtModified;


}

2.2 前台页面动态生成菜单

使用navMenu

<template>
    <div class="navMenu">

        <template v-for="navMenu in navMenus">
            <!-- 最后一级菜单 -->
            <el-menu-item v-if="navMenu.children.length==0"
                          :key="navMenu.id" :data="navMenu" :index="navMenu.path"
            >
                <i :class="navMenu.icon"></i>
                <span slot="title">{{navMenu.name}}</span>
            </el-menu-item>

            <!-- 此菜单下还有子菜单 -->
            <el-submenu v-if="navMenu.children.length!=0"
                        :key="navMenu.id" :data="navMenu" :index="navMenu.path">
                <template slot="title">
                    <i :class="navMenu.icon"></i>
                    <span> {{navMenu.name}}</span>
                </template>
                <!-- 递归 -->
                <NavMenu :navMenus="navMenu.children"></NavMenu>
            </el-submenu>
        </template>

    </div>
</template>

<script>
    export default {
        name: 'NavMenu',
        props: ['navMenus'],
        data() {
            return {}
        },
        methods: {}
    }
</script>

<style>
</style>

前台主页代码:

<template>
    <el-container class="home-container">
        <!-- 头部区域 -->
        <el-header>
            <div>
                <img src="../assets/preview.jpg" height="60px" width="60px"/>
                <span>TenliTenli中心</span>
            </div>
            <el-button type="info" @click="logout"> 退出 </el-button>
        </el-header>
        <!-- 页面主体区域 -->
        <el-container>
            <!-- 侧边栏 -->
            <el-aside width="200px">
                <!-- 侧边栏菜单 -->
                <el-menu
                        background-color="deeppink"
                        text-color="black"
                        active-text-color="skyblue">
                    <NavMenus :navMenus="menus"></NavMenus>
                </el-menu>
            </el-aside>
            <!-- 主体结构 -->
            <el-main>Main</el-main>
        </el-container>
    </el-container>
</template>

<script>
	//导入NavMenu组件
    import NavMenu from "../components/menu.vue";
    export default {
        name: "home",
        components: {
        	//注册NavMenu组件,和上面的标签名一致
            NavMenus: NavMenu
        },
        data(){
            return{
                menus:[],
            }
        },
        created() {
            this.initMenu();
        },
        methods:{
            logout(){
                var that=this;
                this.$http.get("http://localhost:8081/sys/logout").then(function(resp){
                    sessionStorage.clear();
                    that.$router.push("/login")
                });

            },
            initMenu(){
                var that=this;
                this.$http.get("http://localhost:8081/permission/getAllPermissonByUserId").then(function(resp){
                    that.menus=resp.data.result;
                });
            }
        }
    }
</script>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值