若依的使用(第三方登录、@Anonymous?、前端通用方法)

1.第三方登录

当今社会,微信登录、QQ登录、抖音登录等等三方登录已经层出不穷,学会三方登录势在必行。

微信登录要认证开发者,必须为企业,个人不行,而且还要交300块钱。一听到钱,白嫖党的钱是不可能被赚的,不学微信登录。

QQ登录也要申请、微博登录也要申请。

还好Gitee给力,申请轻轻松松,谁都能轻松让Gitee作为第三方登录,此次我们就讲解Gitee来登录ry。其实其他的登录也是基本上一样的。

1.1JustAuth 奥义·穿风刺

JustAuth能让我们第三方登录写少一些代码,它包装了国内外30多种三方登录。

学习JustAuth网站:

https://mp.weixin.qq.com/s?__biz=MzA3NDk3OTIwMg==&mid=2450633106&idx=1&sn=131e39d52347dffefbd4227b18b794bf&chksm=8892937fbfe51a69950cb0769e2b22d04217254b0e79cdcee4204aedb2007627ab6511b58355&token=29120304&lang=zh_CN#rd

https://justauth.wiki/guide/quickstart/how-to-use/#%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4

1.2使用步骤

使用JustAuth总共分三步(这三步也适合于JustAuth支持的任何一个平台):

1、申请注册第三方平台的开发者账号。

我们找到gitee的设置,进入第三方应用,如下:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第1张图片

出来界面如下:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第2张图片

我现在是已经新建好了应用,大家是没有ruoyi-test。大家可以新建自己的应用:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第3张图片

应用主页随便填一个自己的应用页面即可。但是应用回调不能乱填,当我们gitee登录成功之后,gitee会自动跳转到应用回调地址,并且gitee会带上code,利用code可以得到所登录gitee用户信息。

2、创建第三方平台的应用,获取配置信息(accessKey, secretKey, redirectUri)。

上面我们已经创建了应用,自然有了这三个值。

3、使用该工具实现授权登陆。

利用工具先要引入依赖:

                    me.zhyd.oauth            JustAuth            1.16.5        

依赖引入到核心框架(framework)下。

接下来改login.vue,如下:

   
     ...省略其他代码
     
        
   
         :loading="loading"
         size="medium"
         type="primary"
         style="width:100%;"
         @click.native.prevent="handleLogin"
       >
         登 录
         登 录 中...
       
       
         立即注册
       
       
         
       
     

以上,我们添加的代码是:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第4张图片

有了该代码,页面呈现的样子是:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第5张图片

关于图片LOGO下载地址请去随便找一个,或者群文件找找。然后我们看一下点击事件:

    giteeLogin() {      PreLoginByGitee().then(res => {        Cookies.set("user-uuid", res.uuid)        window.location = res.authorizeUrl      })    },

以上是在methods中,我们看到直接请求了PreLoginByGitee:

export function PreLoginByGitee() {  return request({    url: '/PreLoginByGitee',    headers: {      isToken: false    },    method: 'get',  })}

对应的后端接口:

    @GetMapping("/PreLoginByGitee")
   public AjaxResult PreLoginByGitee() {
       AjaxResult ajax = AjaxResult.success();
       AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
               .clientId("1712ae8e8105c0005da36339ed72c1a6aae86322fc64d431dadcaa275a14be45")
               .clientSecret("87fb8f83efc04fcd85696d5461c80ce6f98c94845539f9612024e81302111a05")
               .redirectUri("http://localhost/callback")
               .build());
       String uuid = IdUtils.fastUUID();
       String authorizeUrl = authRequest.authorize(uuid);
       //存储
       ajax.put("authorizeUrl", authorizeUrl);
       ajax.put("uuid", uuid);
       return ajax;

}

以上的代码是生成跳转路径。生成一个gitee的路径,在该页面gitee只要登录完成,gitee程序会自动跳转到我们之前设置好的回调地址。这里我们发现我们一直拿着一个uuid在传来传去,还传去了前端,它有什么鸟用呢?

authRequest.authorize(uuid)用到了uuid,并且后面要执行:

authRequest.login(AuthCallback.builder().state(uuid).code(code).build());

要保证俩uuid为同一个,所以uuid才传来传去。

关于本篇文章,文字有点难以描述,请大家看视频。

上面window.location = res.authorizeUrl让我们进入了如下界面:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第6张图片

只要我们登陆好gitee,gitee会自动跳转到我们的回调地址。此时回调到了前端的下面的路由:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第7张图片

我们还提前准备好了组件:




从上面可以看到,又带着了uuid,执行LoginByGitee方法,如下:

    LoginByGitee({commit}, body) {      return new Promise((resolve, reject) => {        loginByGitee(body.code, body.uuid).then(res => {          setToken(res.token)          commit('SET_TOKEN', res.token)          resolve()        }).catch(error => {          reject(error)        })      })    },

继续追代码:

export function loginByGitee(code, uuid) {  const data = {    code,    source: "Gitee",    uuid  }  return request({    url: '/loginByGitee',    headers: {      isToken: false    },    method: 'post',    data: data  })}

追到底了,调用的后端的:

    @PostMapping("/loginByGitee")
   public AjaxResult loginByGitee(@RequestBody LoginByOtherSourceBody loginByOtherSourceBody) {
       AjaxResult ajax = AjaxResult.success();
       String token = loginService
               .loginByOtherSource(loginByOtherSourceBody.getCode(), loginByOtherSourceBody.getSource(), loginByOtherSourceBody.getUuid());
       ajax.put(Constants.TOKEN, token);
       return ajax;
    }

service层:

   public String loginByOtherSource(String code, String source, String uuid) {
       //先到数据库查询这个人曾经有没有登录过,没有就注册
       // 创建授权request
       AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
               .clientId("1712ae8e8105c0005da36339ed72c1a6aae86322fc64d431dadcaa275a14be45")
               .clientSecret("87fb8f83efc04fcd85696d5461c80ce6f98c94845539f9612024e81302111a05")
               .redirectUri("http://localhost/callback")
               .build());
       AuthResponse login = authRequest.login(AuthCallback.builder().state(uuid).code(code).build());
       System.out.println(login);
       //先查询数据库有没有该用户
       AuthUser authUser = login.getData();
       SysUser sysUser = new SysUser();
       sysUser.setUserName(authUser.getUsername());
       sysUser.setSource(authUser.getSource());
       List sysUsers = userService.selectUserListNoDataScope(sysUser);
       if (sysUsers.size() > 1) {
           throw new ServiceException("第三方登录异常,账号重叠");
       } else if (sysUsers.size() == 0) {
           //相当于注册
           sysUser.setNickName(authUser.getNickname());
           sysUser.setAvatar(authUser.getAvatar());
           sysUser.setEmail(authUser.getEmail());
           sysUser.setRemark(authUser.getRemark());
           userService.registerUserAndGetUserId(sysUser);
           AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER,
                   MessageUtils.message("user.register.success")));
       } else {
           sysUser = sysUsers.get(0);
       }
       AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
       //注册成功或者是已经存在的用户
       LoginUser loginUser =
               new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissionService.getMenuPermission(sysUser));
       recordLoginInfo(loginUser.getUserId());
       // 生成token
       return tokenService.createToken(loginUser);

}

似乎如此就完事了。但实际上有很多细节我都没说。在视频里面说。

细节1:前端白名单放行:

const whiteList = ['/login', "/callback", '/auth-redirect', '/bind', '/register']

细节2:字段source。如下代码可以获得登录source:

AuthUser authUser = login.getData();

source表示登录平台,如微信登录,支付宝登录,因为要确定用户的唯一性。username在不同的平台可能会重复,但是username+source就不会重复了。

细节3:新写了一个查询方法,如下:

userService.selectUserListNoDataScope(sysUser);
    @Override
   public List selectUserListNoDataScope(SysUser user) {
       return userMapper.selectUserList(user);

}

新重写一个查询的原因是原来的的方法有数据权限:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第8张图片

细节4:安全配置放行了若干接口:

                .antMatchers("/login", "/register", "/captchaImage", "/loginByGitee", "/PreLoginByGitee").anonymous()

细节5:当我们第一次登录成功,什么权限都没有,需要admin设置一下权限,或者在注册的时候给新用户一个“普通角色”。

细节6:新登录用户,头像根本显示不了,改成如下就能显示了【文件user.js】:

          let avatar;
         if (user.avatar == "" || user.avatar == null) {
           avatar = require("@/assets/images/profile.jpg")
         } else if (user.avatar.startsWith("http")) {
           avatar = user.avatar
         } else {
           avatar = process.env.VUE_APP_BASE_API + user.avatar;

 }

其他细节不再赘述。

2、新建后端模块与包名的修改

权限和第三方登录确实令人头疼,我们来学一点简单一点的。

另外,如果各位有属于自己的域名和ICP/IP备案,布置一个作业,自行实现第三方QQ登录。

2.1 包名的修改

我们所说的包名修改,是一次性修改ruoyi的全部包名,因为发现很多人有这样的需求,下载别人的代码,想要改成自己公司的包名,结果一改就各种出现问题,程序都起不来了,气死。

给大家介绍一款工具,下载它能一键修改:

https://gitee.com/lpf_project/common-tools

https://gitee.com/lpf_project/common-tools/releases/tag/V4-20220517

通过修改器可以一键修改包名和外层文件夹名字。这里文字不再赘述。请直接看视频演示。

2.2后端模块的新建

发现许多情况下,若干同学们都想新建一个模块,然后不影响ry原先的代码,下面就演示步骤:

对准项目根文件右键新建选择模块:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第9张图片

然后新建模块,注意父模块要选ruoyi,名称随便取一个,但是要符合起名习惯,我现在取名字为wqj:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第10张图片

值得注意的是,新建的模块需要被admin模块所引入:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第11张图片

如上,这样就可以了,然而,发现ruoyi的其他依赖项都没有加版本号,它们都从父模块继承了版本!下面我们去父模块写好版本!:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第12张图片

写好之后,子模块就可以不写版本了。

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第13张图片

左上角也出现了一个继承的标志。

2.3 现在已经有了新的模块了,那么,我需要添加我自己的代码有什么注意事项吗?

根包的建立

因为RuoYiApplication在com.ruoyi包下,所以我们也需要手动建立com.ruoyi包,不然自己写的类无法注入Spring容器。

依赖

关于依赖,说的是什么呢?我新建的ruoyi-wqj可能或多或少依赖于ruoyi-framework或者ruoyi-common,当我们引入依赖的时候,一定不要造成循环依赖。

循环依赖会造成打包直接报错。循环依赖就是说A依赖B,B又依赖A。

当我们自己新建的模块需要依赖ruoyi-system和ruoyi-framework的时候,只需要引入ruoyi-framework即可,因为ruoyi-framework里面引入了ruoyi-system。但是再引也不会报错,因为没有循环依赖。

2.4 新模块中测试controller

新模块竟然没有Spring,需要引入ruoyi-common:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第14张图片

当我们引入ruoyi-common后一切正常:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第15张图片

经过启动测试,一切正常!

2.5 正常个锤子,访问是这样的:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第16张图片

这里为了让该接口可以匿名访问,我们可以通过一个注解完事:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第17张图片

然后可以访问了:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第18张图片

3、 @Anonymous原理

大家应该已经习惯我的教学套路,很多时候都是先使用,然后讲述原理。

上节课我们使用了注解@Anonymous,然后接口就可以直接被访问到了,不用token!不用token!不用token!。

我们一般知道,注解是给程序看的,给机器看的,当然也是给程序员看的。注解如果没有注解解析器(注解处理器,注解解释器),那么注解就没有什么作用。所以@Anonyous一定是在某个地方被干嘛干嘛了!

先来看一波@Anonyous的源码:

/**
* 匿名访问不鉴权注解
*
* @author ruoyi
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{

}

从源码可以看到,它可以放到类上,或者方法上。

那么我们就可以这样想:放到类上,该类所有方法都可以匿名访问;放到方法上,那么就该方法可以被匿名访问。

下面直接上注解解析器:

/**
* 设置Anonymous注解允许匿名访问的url
*
* @author ruoyi
*/
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
   private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
   private ApplicationContext applicationContext;
   private List urls = new ArrayList<>();
   public String ASTERISK = "*";
   @Override
   public void afterPropertiesSet()
{
       RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
       Map map = mapping.getHandlerMethods();
       map.keySet().forEach(info -> {
           HandlerMethod handlerMethod = map.get(info);
           // 获取方法上边的注解 替代path variable 为 *
           Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
           Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
                   .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
           // 获取类上边的注解, 替代path variable 为 *
           Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
           Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns()
                   .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
       });
   }
   @Override
   public void setApplicationContext(ApplicationContext context) throws BeansException
{
       this.applicationContext = context;
   }
   public List getUrls()
{
       return urls;
   }
   public void setUrls(List urls)
{
       this.urls = urls;
   }

}

从以上的代码我们可以发现,这个类实现了两个接口。

InitializingBean接口实现之后,初始化bean的时候会执行接口中的方法afterPropertiesSet。

ApplicationContextAware接口实现之后,可以重写setApplicationContext方法,该方法可以拿到Spring的上下文,相当于Spring容器在手,天下我有。

所以我们打一个断点,然后重新启动项目,断点就会卡住。然后直接debug讲述。

可以很容易发现,无非是先拿到一个包含所有映射和HandlerMethod的(只读)映射。

通过拿到映射后,获得每个映射的HandlerMethod,也就是获得处理它的方法。然后看看该方法有没有Anonymous.class注解,有的话保存成method变量,然后后续校验后加入到urls的ArrayList中保存起来供后续使用。

3.1 值得注意的是:

这里涉及到一个正则表达式,处理了特殊情况。当我们的url为 /{getget} 的时候,会被替换为/*,当我们的url为/{getget}/{abc}的时候,会被替换为/*/*。

正则表达式为:\{(.*?)\}

相关链接:https://segmentfault.com/q/1010000010680178。

后续在SecurityConfig中把访问权限改成了permitAll:​​​​​​​

        // 注解标记允许匿名访问的url
       ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();

permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAl

4、前端通用方法

讲了好多难的东西,接下来我们来轻松一点。

讲一下前端的通用方法

所谓通用方法,就是随时能调用的方法。

例如:​​​​​​​

this.$tab.openPage("用户管理", "/system/user");this.$modal.msg("默认反馈");this.$auth.hasPermi("system:user:add");this.$auth.hasRole("admin");this.$cache.local.set('key', 'local value')this.$download.name(name);

为什么说这些方法可以随时调用呢?

首先,ry准备好了插件:​​​​​​​

import tab from './tab'
import auth from './auth'
import cache from './cache'
import modal from './modal'
import download from './download'
export default {
 install(Vue) {
   // 页签操作
   Vue.prototype.$tab = tab
   // 认证对象
   Vue.prototype.$auth = auth
   // 缓存对象
   Vue.prototype.$cache = cache
   // 模态框对象
   Vue.prototype.$modal = modal
   // 下载文件
   Vue.prototype.$download = download
 }

}

然后在main.js调用了安装插件方法:​​​​​​​

...省略其他代码
import plugins from './plugins' // plugins
...省略其他代码
Vue.use(plugins)

...省略其他代码

我们知道,Vue.use(plugins),会去自动调用plugins中的install方法,在install方法中,发现Vue.prototype上面装了一些东西。

到此我们明白了,Vue原型上挂载了,所以随时能访问到。

为什么原型挂载就能访问到呢?:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第19张图片

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第20张图片

以上是尚硅谷的Vue视频曾提到的一个内置关系。如果不明白Vue原型挂载为啥能访问的只能自行去看视频了。

4.1$tab对象

http://doc.ruoyi.vip/ruoyi-vue/document/qdsc.html#tab%E5%AF%B9%E8%B1%A1

下面我们亲自编码测试一下如何使用即可。

1、打开新页面​​​​​​​

    // 单纯打开新页面
   openTZGG() {
     this.$tab.openPage("打开的通知公告", "/system/notice");
   },
   // 打开页面之后,并且做一点其他事情。
   openTZGGAndWindows() {
     this.$tab.openPage("打开的通知公告并且弹窗", "/system/notice").then(() => {
       this.$modal.msg("我是弹窗")
     })

}

看看源码:​​​​​​​

  // 添加tab页签
 openPage(title, url, params) {
   var obj = { path: url, meta: { title: title } }
   store.dispatch('tagsView/addView', obj);
   return router.push({ path: url, query: params });

},

从上面的源码可以看出,做了两件事。

第一件事:

调用Vuex的tagsView/addView方法,并传参数obj。该方法是为了把新打开的页签添加到Vuex维护的页签数组visitedViews。

当添加成功,组件TagsView的计算属性visitedViews变化,页面重新渲染,页签自然多一个。

第二件事:

第二件事就是将路由做简单的跳转。

4.2 修改页签​​​​​​​

    updateMyself() {
     //修改自己
     const obj = Object.assign({}, this.$route, {title: "自定义标题"})
     this.$tab.updatePage(obj);
   },
   updateTZGG() {
     // 修改别的
     const obj = Object.assign({}, {path: "/system/notice"}, {title: "自定义标题222"})
     this.$tab.updatePage(obj).then(() => {
       this.$modal.msg("修改页签完毕")
     })

},    

4.3 关闭页签​​​​​​​

    //关闭当前并且打开新页面
   closeAndOpen() {
     const obj = {path: "/system/user"};
     this.$tab.closeOpenPage(obj);
   },
   //关闭当前并回到首页
   closeAndGoIndex() {
     this.$tab.closePage();
   },
   //关闭指定的页面
   closeSpecifyPage() {
     const obj = {path: "/system/user"};
     this.$tab.closePage(obj);

}

4.4 刷新页签​​​​​​​

    //刷新当前页签    refreshPage() {      this.$tab.refreshPage();    }

刷新页签相当于页面关闭,重新打开。

4.5 关闭所有页签​​​​​​​

    //关闭所有页签    closeAll() {      this.$tab.closeAllPage();    },

4.6 关闭左侧​​​​​​​

    //关闭所有页签
   closeAll() {
     this.$tab.closeAllPage().then(res => {
         this.$tab.openPage("shouye", "/index")
       }
     );

},

4.7 关闭右侧​​​​​​​

    //关闭右侧
   closeRight() {
     this.$tab.closeRightPage();

},

4.8 关闭其他​​​​​​​

    //关闭其他
   closeOther() {
     this.$tab.closeOtherPage();

5、$modal对象

官方文档已经很齐备了:

http://doc.ruoyi.vip/ruoyi-vue/document/qdsc.html#modal%E5%AF%B9%E8%B1%A1

下面我们亲自编码测试一下如何使用即可。

提供成功、警告和错误等反馈信息​​​​​​​

    msg() {
     this.$modal.msg("默认反馈");
     // this.$modal.msgError("错误反馈");
     // this.$modal.msgSuccess("成功反馈");
     // this.$modal.msgWarning("警告反馈");

},  

提供成功、警告和错误等提示信息​​​​​​​

    alert() {
     this.$modal.alert("默认提示");
     // this.$modal.alertError("错误提示");
     // this.$modal.alertSuccess("成功提示");
     // this.$modal.alertWarning("警告提示");

},    

提供成功、警告和错误等通知信息​​​​​​​

    notify() {      this.$modal.notify("默认通知");      // this.$modal.notifyError("错误通知");      // this.$modal.notifySuccess("成功通知");      // this.$modal.notifyWarning("警告通知");    },

提供确认窗体信息​​​​​​​

    confirm() {
     this.$modal.confirm('你确定要确定吗').then(function () {
     }).then(() => {
       this.$modal.notify("点了确定")
     }).catch(() => {
       this.$modal.notify("点了取消")
     });

},  

提供遮罩层信息​​​​​​​

   //遮罩层
   startLoading() {
     // 打开遮罩层
     this.$modal.loading("正在Loading,请稍后...");
     wait5Second().then(res => {
       console.log(res)
       this.$modal.closeLoading();
     })
   },
   //如若已经loading则只能程序自己调用
   stopLoading() {
     this.$modal.closeLoading();

}

5.1 $auth对象

该对象用于鉴权。

官方说明:

http://doc.ruoyi.vip/ruoyi-vue/document/qdsc.html#auth%E5%AF%B9%E8%B1%A1

验证用户权限​​​​​​​

    validMyPermission() {
     console.log(this.$auth.hasPermi("system:user:list"))

} ​​​​​​​

验证用户角色​​​​​​​

    validMyRole() {
     this.$modal.notify(this.$auth.hasRole("admin") + "")

}

验证用户角色的源码:​​​​​​​

function authPermission(permission) {
 const all_permission = "*:*:*";
 const permissions = store.getters && store.getters.permissions
 if (permission && permission.length > 0) {
   return permissions.some(v => {
     return all_permission === v || v === permission
   })
 } else {
   return false
 }

}​​​​​​​

以上就是检查用户传入的权限到底有没有。

5.2$cache对象

$cache看看就可以了,甚至笔者都不想演示。

$cache对象用于处理缓存。我们并不建议您直接使用sessionStorage或localStorage,因为项目的缓存策略可能发生变化,通过$cache对象做一层调用代理则是一个不错的选择。$cache提供session和local两种级别的缓存,如下:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第21张图片

​​​​​​​

// local 普通值
this.$cache.local.set('key', 'local value')
console.log(this.$cache.local.get('key')) // 输出'local value'
// session 普通值
this.$cache.session.set('key', 'session value')
console.log(this.$cache.session.get('key')) // 输出'session value'
// local JSON值
this.$cache.local.setJSON('jsonKey', { localProp: 1 })
console.log(this.$cache.local.getJSON('jsonKey')) // 输出'{localProp: 1}'
// session JSON值
this.$cache.session.setJSON('jsonKey', { sessionProp: 1 })
console.log(this.$cache.session.getJSON('jsonKey')) // 输出'{sessionProp: 1}'
// 删除值
this.$cache.local.remove('key')

this.$cache.session.remove('key')

5.3 $download对象

$download对象用于文件下载,它定义在plugins/download.js文件中,它有如下方法

根据名称下载download路径下的文件​​​​​​​

    downloadByName() {
     const name = "1.txt";
     const isDelete = true;
     // 默认下载方法
     this.$download.name(name);
     // 下载完成后是否删除文件
     // this.$download.name(name, isDelete);

}

以上的this.$download.name(name)方法在ry中它自己却没有用过。

那么如何正确使用它呢?:​​​​​​​

    testDownload(){
     //通过sheng_cheng_wen_jian方法去调用后端某一个接口,后端把文件保存在ruoyi/download/下面,
     // 然后后端返回文件名,然后前端收到文件名之后才去真正的下载。
     //然而既然要重新调用第二次下载接口,为什么不一次性解决呢?所以这个接口不是那么好用。
     sheng_cheng_wen_jian().then(res=>{
       this.$download.name(res.name)
     })

}

根据名称下载upload路径下的文件

我们可以看到,官网就发了下面这段代码:​​​​​​​

const resource = "/profile/upload/2021/09/27/be756b96-c8b5-46c4-ab67-02e988973090.png";
// 默认方法
this.$download.resource(resource);

事实上,通过该方法可以下载到ruoyi/upload文件夹下面的若干文件。

我们观察到,作者的例子中带着日期什么的,其实这些数据都是存在数据库中的。

有时候我们要存用户的头像怎么办呢?我们就可以把用户头像的URI存到数据库中,然后通过该URI直接在前端展示,当然也可以通过resource方法下载下来。下面我们通过一个简单的案例来说明。

6、建立数据表​​​​​​​

CREATE TABLE `ruoyi-vue`.`test`  (
 `id` bigint NOT NULL COMMENT '主键',
 `uri` varchar(255) NULL COMMENT 'URI',
 PRIMARY KEY (`id`)
);
-- 下面因为忘记主键自增,回来改的数据库。
ALTER TABLE `ruoyi-vue`.`test`
MODIFY COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键' FIRST;

6.1 浅秀生成代码

请看视频。

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第22张图片

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第23张图片

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第24张图片

以上图片是我生成代码改的一些参数。

经过测试,页面正常,也能显示图片:

图片

此时我们发现,network的请求响应就有一个路径!!!:

若依的使用(第三方登录、@Anonymous?、前端通用方法)_第25张图片

发现就在下面的路径中有我们要的图片:

http://localhost/dev-api/profile/upload/2022/09/08/favicon(1)_20220908150152A001.jpg

该路径为何能显示图片呢?

首先是dev-api走的前端代理,代理到了后端,并且削掉了dev-api的前缀。

找到后端后,后端静态资源是直接映射的。​​​​​​​

   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry)
{
       /** 本地文件上传路径 */
       registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
               .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
       /** swagger配置 */
       registry.addResourceHandler("/swagger-ui/**")
               .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");

}

以上代码是说:

如果收到 /profile/.....的请求,就去D:/ruoyi/uploadPath/下面去找对应的资源文件。

根据请求地址下载zip

ry自己用过该方法,当我们生成代码的时候可以下载,对应的下载zip包:​​​​​​​

    /** 生成代码操作 */
   handleGenTable(row) {
     const tableNames = row.tableName || this.tableNames;
     if (tableNames == "") {
       this.$modal.msgError("请选择要生成的数据");
       return;
     }
     if(row.genType === "1") {
       genCode(row.tableName).then(response => {
         this.$modal.msgSuccess("成功生成到自定义路径:" + row.genPath);
       });
     } else {
       this.$download.zip("/tool/gen/batchGenCode?tables=" + tableNames, "ruoyi");
     }

},

需要保证请求的地址返回的输出流是zip包。

更多文件下载操作​​​​​​​

    template:
   
         
       
         downloadByName
         
   
       
       
       
         自定义文本保存1
         自定义文本保存2
         自定义文本保存3
         自定义文本保存4
       
     
   
   
   
   
   methods:
   
   
   hello() {
     // 自定义文本保存
     const blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
     this.$download.saveAs(blob, "hello world.txt");
   },
   hello1() {
     // 自定义文件保存
     const file = new File(["Hello, world!"], "hello world.txt", {type: "text/plain;charset=utf-8"});
     this.$download.saveAs(file);
   },
   hello2() {
     // 自定义data数据保存
     const data = "中国共产党是世界第一大党!";
     const blob = new Blob([data], {type: 'text/plain;charset=utf-8'})
     this.$download.saveAs(blob, "2.txt")
   },
   hello3() {
     // 根据地址保存文件
     this.$download.saveAs("https://ruoyi.vip/images/logo.png", "logo.jpg");

}

7、

你可能感兴趣的:(javaweb,javascript,vue.js,开发语言)