前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >删除业务与鉴权

删除业务与鉴权

作者头像
时光潜流
发布2022-12-26 19:38:06
1.2K0
发布2022-12-26 19:38:06
举报
文章被收录于专栏:博客专栏博客专栏

    今天主要完成的是动态、专辑与图片的基本删除能力,其中包含了前端右键菜单设计以及导出功能的完善。(专辑=相册)

后端的业务实现

删除动态

    这个的实现最为简单,没有上面特别需要注意的地方,所以不多赘述。

删除专辑

    删除专辑中,由于有设定数据库中前三个专辑id的默认匹配规则(自动、博客、动态),所以在删除的时候给了id>3的约束,以免误删。同时,在删完该专辑确认成功后,才能继续将该专辑内的图片记录也都删除,否则除非事务回退,不然先删除图片记录是不可逆的,所以必须先进行专辑删除状态的确认才能删除图片。

删除图片

    删除图片的业务也相对没有什么特别的,只需要注意不要删除id=1的记录即可,因为id=1的图片记录将作为专辑的封面。

参数的注意点

    今天在控制器的一个方法参数中,使用Integer作为请求参数类型获取id,但是发现此时如果用户没有传递id值,Integer作为引用类型也会默认赋值null,而不会被报错,需要注意!所以如果该参数是必须的,还是应该设置为int类型的(仅控制器,因为控制器能通过说明必然有值了)。

前端的业务实现

事件对象函数的进一步说明

    昨天有说到需要获取事件的对象时,调用函数不带(),那么默认的第一个参数即为事件对象。

    但是今天遇到了既要传递参数又要对象的情况,这种情况的方式也很简单,传递参数的时候,第一个参数设置为$event即可。

动态页上图

    想来想去,最后还是决定动态也用博客页的编辑模板进行编辑,自我约束动态页格式不要太花里胡哨的就好了,原本其实是打算像QQ空间一样的上图设计方式,但是,嗯,感觉那样的方式不如直接用博客页编辑模板来的方便和自由,所以最后动态页上图也就简单的改一下模板就完成了。

前台回忆页图片展示

    前台回忆页的图片细节展示也摸了,直接利用后台查看图片的模板,稍作修改就完成了,这叫提高开发效率,绝对不是偷懒!一寸光阴一寸金,寸金难买寸光阴!

编辑页的文本导出

    为什么要导出文本编辑的内容?那当然是防止突然断网啊、突然缺少灵感啊什么不得不停笔的情况了。

    你绝对想象不到这篇博客之前版本的我是怎么导出备份的!(输出到控制台,手动复制粘贴到文件QAQ)

    经过一些资料的查询后,得到了最终的解决方式,那就是a链接下载,a链接有download属性,表示的是下载的文件名,href则是目标地址。不过对于则个目标地址,学问可太大了。

代码语言:javascript
复制
const myBlob = new Blob([this.editor.txt.html()])

const toDownload = document.createElement('a')

toDownload.download = 'log' + this.$time() + '.txt'
toDownload.href = URL.createObjectURL(myBlob)
toDownload.click()
document.body.removeChild(myBlob)

    首先是将需要保存的数据内容封装成blob类型,需要传递的是数组,所以要用[]括起来。然后利用URL.createObjectURL(Blob)将数据对象创建仅限当前页的URL路径,可以给a链接href赋值,之后调用a链接的click函数触发点击事件。最后将这个url创建出来的blob节点给删除掉。

后台动态删除

    后台动态的删除和后端的一样非常简单,不多赘述。

图片与专辑的删除

    这个稍微设计了一活儿,主要就是设计了一个右键弹出菜单的效果,利用@click.right.prevent来禁用原来的效果。目前设计的比较简单,或者说后台也就只有我能看,所以我能看的懂就行,当然以后从业时不能有这种想法,因为那时,我做出来的就是给别人的了。

    先来上图(实现了上传图片功能就是拽啊,随手上图( ̄y▽, ̄)╭ )[我是绝对不会说我在上传这张图时遇到了bug的,哼哧哼哧]

    怎样,是不是很简单的菜单实现,当完美右击指定专辑或者图片的时候,就获取clientX/Y并且记录是专辑类型还是图片类型以及其id号,然后将这个绝对定位的菜单的top和left对应设置就好了。然后全局加个click监听,当全局收到click时,这个菜单的v-show就设置为false,用来灵活的进行唤出。

html

代码语言:javascript
复制
<div class="menu" ref="menu" v-show="showMenu">
  <p>编辑</p>
  <p @click="delTarget">删除</p>
  <p>取消</p>
</div>

scss

代码语言:javascript
复制
.menu{
  position:absolute;
  width:100px;
  background-color:white;
  text-align: center;
  border-radius: 6px;
  cursor: default;
  p{
    border-bottom: 1px solid gray;
    padding: 2px;
  }
}

js

代码语言:javascript
复制
targetDeal (e, id, isAlbum) {
  this.targetMenuAlbum = isAlbum
  this.targetMenuId = id
  const node = this.$refs.menu
  node.style.top = e.clientY + 'px'
  node.style.left = e.clientX + 'px'
  this.showMenu = true
}

鉴权

    拖更到今天终于到鉴权了,之前没写是因为那时候这个博客栏目还没有做好,现在做好了所以补录一下。

    鉴权当然有着很多个板块,大的方向来说就是前端管理员页面鉴权。后端对一些需要鉴权的api请求拦截,以及图床请求需要AccessToken。下面来一个一个说。

图床鉴权

1. 签名

    签名的算法如下 signStr = 请求URI + 请求参数 + "\n" + 请求体(没有请求体也要 "" 貌似,看图床的api这样说的)

    签名 (sign)16 = hmac_sha1(signStr, <YOUR_SECRET_KEY>)

注意:请求体千万注意每个字节都要,如果直接getBytes()然后转String,那么一些隐藏的字符被忽略,并且结果数据内容也完全不对,这样是无法通过签名验证的!

2. accessToken

    结果 accessToken = "TOKEN " + <YOUR_ACCESS_KEY> + ":" + (sign)16

    实际的java算法示例:

代码语言:javascript
复制
=> (byte[] bytes,String secretKey,String apiPath)

String signStr = apiPath + "\n";
ByteArrayOutputStream bao = new ByteArrayOutputStream();
bao.write(signStr.getBytes());
bao.write(bytes);
byte[] res = bao.toByteArray();
bao.close();

String sign = "";
try {
	Mac mac = Mac.getInstance("HmacSHA1");
	mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA1"));
	sign = new String(new Hex().encode(mac.doFinal(res)), StandardCharsets.UTF_8);
	// 这里 Hex 来自 org.apache.commons.codec.binary.Hex
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
	e.printStackTrace();
	throw new RuntimeException(e.getMessage());
}
String authorization = "TOKEN " + accessKey + ':' + sign;

    之后每当发送给服务方需要鉴权的api请求时,带上Authrization请求头即可达成通信(代码有借鉴与更改)。

后端请求鉴权

   后端请求鉴权的方式熟悉拦截器那就非常简单了。

首先是产生token

代码语言:javascript
复制
@Override
public RetResult&lt;String&gt; checkPassword(String username, String password) {
  ValueOperations op = redisTemplate.opsForValue();
  
  String admin = (String) op.get("admin");
  String pass = (String) op.get("password");
  
  if(admin == null || pass == null) return RetResult.notFound();
  else if (!admin.equals(username)) return RetResult.fail("用户不正确");
  else if (!pass.equals(password)) return RetResult.fail("密码不正确");
  else{
  	String token = Md5String.md5WithTime(username);
  	op.set("token",token,10, TimeUnit.DAYS);
  	return RetResult.success(token);
  }
}

其中Md5String是自己定义的工具类

代码语言:javascript
复制
package top.dreamcenter.dreamcenter.utils;

import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.util.DigestUtils;
import sun.security.provider.MD5;

public class Md5String {
    public static String md5WithTime(String src){
        src = src + System.currentTimeMillis();
        return DigestUtils.md5DigestAsHex(src.getBytes());
    }
}

拦截器如下

代码语言:javascript
复制
package top.dreamcenter.dreamcenter.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import top.dreamcenter.dreamcenter.ret.RetResult;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AdminAuthInterceptor implements HandlerInterceptor {

    private RedisTemplate template;

    @Autowired
    public AdminAuthInterceptor(RedisTemplate template) {
        this.template = template;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader("Authorization");
        String token = (String) template.opsForValue().get("token");
        if(authorization!=null && authorization.equals(token)) return true;

        response.setContentType("application/json;charset=utf8");

        ObjectMapper mapper = new ObjectMapper();
        String res = mapper.writeValueAsString(RetResult.denied());
        response.getWriter().write(res);

        return false;
    }
}

之后给容器配置添加完美的拦截器即可

代码语言:javascript
复制
package top.dreamcenter.dreamcenter.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.dreamcenter.dreamcenter.interceptor.AdminAuthInterceptor;

@Configuration
public class ToolsConfig implements WebMvcConfigurer{

    private RedisTemplate redisTemplate;

    @Autowired
    public ToolsConfig(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AdminAuthInterceptor(redisTemplate))
                .addPathPatterns("/api/info/**")
                .addPathPatterns("/api/*/add")
                .addPathPatterns("/api/*/delete")
                .addPathPatterns("/api/image/upload");
    }

}

前端管理员页鉴权

   这个部分有一说一还是错综复杂的,因为需要考虑的东西相对来说比较多。

   假如我们登录通过鉴权获得token后,我默认的设置会保存在session中,也就是当前会话,但是如果选择了记住密码,那么肯定是要保存在cookie中的,这样,下一次打开浏览器,到我们的domain域下,就会自动的从cookie中获取保存的token并且放置到session中,当然是放到session中啦,因为session会比cookie来的更及时一点,比如说更改密码,也是session最先奏效,而cookie是否更改和续期则要取决于需求了,而且统一调用session会比一活儿调用session(无cookie有session时)一活儿调用cookie来的要合适,个人理解。之后我们已有token于session中后,可以选择性的请求头携带Authorization,也可以干脆每个请求头都带,需要注意的是,如果每个都带,那么无需鉴权的请求也带,从某些方面来说,有一部分数据的冗余,增加了流量消耗。

    好了,有了鉴权后如何前端如何拦截未通过用户进入管理员页面,当然可以用路由守卫,不过我则是在beforeMount也就是装载之前时期,请求一个需要鉴权的api,如果被拒绝那么就跳转到登录页或者模式。

    下面是一些前端鉴权代码的方向:

给每个请求带Authorization

代码语言:javascript
复制
axios.interceptors.request.use((config) => {
  const token = sessionStorage.getItem('token')
  if (token) config.headers.Authorization = token
  return config
})

借着在App.vue中初始化挂载节点时查看是否有token的cookie

代码语言:javascript
复制
beforeMount () {
  const token = this.$cookie.get('token')
  if (token) sessionStorage.token = token
}

   突然发现前端鉴权虽然描述了一大堆,但是实际上却只有一丁点需要分享的23333,而后端没啥描述却有一堆需要分享的代码,也许这也暗示了:前端如美丽动人的妹子,后端如无人问津的死肥宅了吧!(我自己说的,我决定把这句话记入我的名言之一!我可太牛了,随随便便就能说出名言不是)

    好啦!今天的分享也结束了,算是提起那完成了不少工作?明后两天需要完成各项编辑业务以及一则统计业务,不知道又会有什么面对着我呢!

    对了,明天把后台博客页tag标签页添加的效果代码也分享一下,感觉自己设计的用起来蛮舒服的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 后端的业务实现
    • 删除动态
      • 删除专辑
        • 删除图片
          • 参数的注意点
          • 前端的业务实现
            • 事件对象函数的进一步说明
              • 动态页上图
                • 前台回忆页图片展示
                  • 编辑页的文本导出
                    • 后台动态删除
                      • 图片与专辑的删除
                      • 鉴权
                        • 图床鉴权
                          • 后端请求鉴权
                            • 前端管理员页鉴权
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档