Spring Boot + Vue3前后端分离实战wiki知识库系统<十二>--用户管理单点登录开发一

news2024/11/25 20:55:54

目标:

在上一次Spring Boot + Vue3前后端分离实战wiki知识库系统<十一>--文档管理功能开发三我们已经完成了文档管理的功能模块开发,接下来则开启新模块的学习---用户登录,这块还是有不少知识点值得学习的,先来看一下整体的效果,关于效果官网有一个体验地址:wiki.courseimooc.com,如下:

其效果也是人人熟知的,下面直接开撸。

用户表设计与持久层代码生成:

用户表设计:

一个模块的开始通常就是从表的设计开始,这里先来将用户表的sql贴出来,当然实际对于表设计肯定不会直接给出个sql,表的设计其实也是非常有学问的,这里重点是操练功能,所以直接贴sql了:

-- 用户表
drop table if exists `user`;
create table `user`
(
    `id`         bigint      not null comment 'ID',
    `login_name` varchar(50) not null comment '登陆名',
    `name`       varchar(50) comment '昵称',
    `password`   char(32)    not null comment '密码',
    primary key (`id`),
    unique key `login_name_unique` (`login_name`)
) engine = innodb default charset = utf8mb4 comment ='用户';

然后执行一下sql:

此时查看一下数据库中用户表有木有生成?

妥妥的,然后我们默认生插入一个用户数据:

看一下表数据:

持久层代码生成:

接下来则来生成持久层的代码,这块在之前已经用得很熟练了,就不过多说明:

然后执行此命令开始生成:

此时看一下本地生成的文件是否正常生成?

在这里我其实一直有一个反思:对于一个初学者来说,这样逃避手写sql层的代码是不是不利于自己的学习呀,其实我看了下公司Java后端的代码是没有使用这种自动生成的方式的,但是,多学一种“高效率”的方式有利无害呀,毕竟对于实际做项目来说效率是非常重要的,哪怕公司里没用到,到时写自己的项目是有可能用到的呀,总之,拥抱变化,任何学到的新知识,在未来总会发挥它的余热的~~

完成用户表基本增删改查功能:

如之前实现电子书和文档的增删改查功能一样,使用非常厉害的CV大法就可以了,这边不厌其烦地再来走一遍流程,下面开始。

后端代码:

1、UserController:

这里从EbookController拷贝过来:

然后再替换一下里面的内容,两个步骤,还记得么?

此时代码中一堆红,不要理,之后随着全局替换完都会自动消失了。

2、UserService:

同样拷贝至EbookService:

然后里面的内容也是那两步进行全局替换,这里就不说明了,替换完成之后,有一个点这里需要修改一下:

3、UserQueryReq:

这里查询只需根据用户名查,这里直接贴内容:

package com.cexo.wiki.req;

public class UserQueryReq extends PageReq {

    private String loginName;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    @Override
    public String toString() {
        return "UserQueryReq{" +
                "loginName='" + loginName + '\'' +
                "} " + super.toString();
    }
}

4、UserQueryResp:

这个直接从domain中的User类中拷贝既可:

5、UserSaveReq:

它也可以拷至EbookSaveReq,不过表单校验规则需要改一下,这里直接将内容贴出:

package com.cexo.wiki.req;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

public class UserSaveReq {
    private Long id;

    @NotNull(message = "【用户名】不能为空")
    private String loginName;

    @NotNull(message = "【昵称】不能为空")
    private String name;

    @NotNull(message = "【密码】不能为空")
    // @Length(min = 6, max = 20, message = "【密码】6~20位")
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】至少包含 数字和英文,长度6-32")
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", loginName=").append(loginName);
        sb.append(", name=").append(name);
        sb.append(", password=").append(password);
        sb.append("]");
        return sb.toString();
    }
}

其中有个小细节需要说明一下:

最后需要解决一个报错:

对于用户来说只需要根据用户名称来查询,所以需要改一下条件,如下:

前端代码:

1、 admin-user.vue:

它的内容同样可以拷贝至电子书的,里面的内容需要做一些减法,因为它没有像电子书中的树形分类数据,这里直接把内容贴一下:

<template>
  <a-layout>
    <a-layout-content
        :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    >
      <p>
        <a-form layout="inline" :model="param">
          <a-form-item>
            <a-input v-model:value="param.loginName" placeholder="登陆名">
            </a-input>
          </a-form-item>
          <a-form-item>
            <a-button type="primary" @click="handleQuery({page: 1, size: pagination.pageSize})">
              查询
            </a-button>
          </a-form-item>
          <a-form-item>
            <a-button type="primary" @click="add()">
              新增
            </a-button>
          </a-form-item>
        </a-form>
      </p>
      <a-table
          :columns="columns"
          :row-key="record => record.id"
          :data-source="users"
          :pagination="pagination"
          :loading="loading"
          @change="handleTableChange"
      >
        <template v-slot:action="{ text, record }">
          <a-space size="small">
            <a-button type="primary" @click="edit(record)">
              编辑
            </a-button>
            <a-popconfirm
                title="删除后不可恢复,确认删除?"
                ok-text="是"
                cancel-text="否"
                @confirm="handleDelete(record.id)"
            >
              <a-button type="danger">
                删除
              </a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </a-table>
    </a-layout-content>
  </a-layout>

  <a-modal
      title="用户表单"
      v-model:visible="modalVisible"
      :confirm-loading="modalLoading"
      @ok="handleModalOk"
  >
    <a-form :model="user" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
      <a-form-item label="登陆名">
        <a-input v-model:value="user.loginName" :disabled="!!user.id"/>
      </a-form-item>
      <a-form-item label="昵称">
        <a-input v-model:value="user.name"/>
      </a-form-item>
      <a-form-item label="密码" v-show="!user.id">
        <a-input v-model:value="user.password"/>
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script lang="ts">
import {defineComponent, onMounted, ref} from 'vue';
import axios from 'axios';
import {message} from 'ant-design-vue';
import {Tool} from "@/util/tool";

export default defineComponent({
  name: 'AdminUser',
  setup() {
    const param = ref();
    param.value = {};
    const users = ref();
    const pagination = ref({
      current: 1,
      pageSize: 10,
      total: 0
    });
    const loading = ref(false);

    const columns = [
      {
        title: '登陆名',
        dataIndex: 'loginName'
      },
      {
        title: '名称',
        dataIndex: 'name'
      },
      {
        title: '密码',
        dataIndex: 'password'
      },
      {
        title: 'Action',
        key: 'action',
        slots: {customRender: 'action'}
      }
    ];

    /**
     * 数据查询
     **/
    const handleQuery = (params: any) => {
      loading.value = true;
      // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
      users.value = [];
      axios.get("/user/list", {
        params: {
          page: params.page,
          size: params.size,
          loginName: param.value.loginName
        }
      }).then((response) => {
        loading.value = false;
        const data = response.data;
        if (data.success) {
          users.value = data.content.list;

          // 重置分页按钮
          pagination.value.current = params.page;
          pagination.value.total = data.content.total;
        } else {
          message.error(data.message);
        }
      });
    };

    /**
     * 表格点击页码时触发
     */
    const handleTableChange = (pagination: any) => {
      console.log("看看自带的分页参数都有啥:" + pagination);
      handleQuery({
        page: pagination.current,
        size: pagination.pageSize
      });
    };

    // -------- 表单 ---------
    const user = ref();
    const modalVisible = ref(false);
    const modalLoading = ref(false);
    const handleModalOk = () => {
      modalLoading.value = true;
      axios.post("/user/save", user.value).then((response) => {
        modalLoading.value = false;
        const data = response.data; // data = commonResp
        if (data.success) {
          modalVisible.value = false;

          // 重新加载列表
          handleQuery({
            page: pagination.value.current,
            size: pagination.value.pageSize,
          });
        } else {
          message.error(data.message);
        }
      });
    };

    /**
     * 编辑
     */
    const edit = (record: any) => {
      modalVisible.value = true;
      user.value = Tool.copy(record);
    };

    /**
     * 新增
     */
    const add = () => {
      modalVisible.value = true;
      user.value = {};
    };

    const handleDelete = (id: number) => {
      axios.delete("/user/delete/" + id).then((response) => {
        const data = response.data; // data = commonResp
        if (data.success) {
          // 重新加载列表
          handleQuery({
            page: pagination.value.current,
            size: pagination.value.pageSize,
          });
        } else {
          message.error(data.message);
        }
      });
    };

    onMounted(() => {
      handleQuery({
        page: 1,
        size: pagination.value.pageSize,
      });
    });

    return {
      param,
      users,
      pagination,
      columns,
      loading,
      handleTableChange,
      handleQuery,

      edit,
      add,

      user,
      modalVisible,
      modalLoading,
      handleModalOk,

      handleDelete,
    }
  }
});
</script>

<style scoped>
img {
  width: 50px;
  height: 50px;
}
</style>

里面的内容都是之前学过了,没有任何难点,所以不过多解释。

2、index.ts添加路由信息:

3、头部增加用户管理菜单入口:

4、整体运行:

用户名重复校验与自定义异常:

概述:

对于用户名,在后端我们表设计时是设置了它的唯一性,不能重复的:

而目前我们并没有做重复名称的校验,看一下:

界面一直转圈,此时后端报异常了:

所以接下来咱们做一下重复用户名的校验逻辑。

实现:

1、后端插入进行用户名的校验:

目前在插入逻辑中并没有根据用户名参数到数据库中查询是否存在:

咱们先提供一个根据用户名查询的方法:

然后插入逻辑就可以修改为:

2、新建业务异常:

接下来这个用户名已存在的业务异常,则采用自定义异常的方式来处理,先来新建一个自定义的异常类:

package com.cexo.wiki.exception;

public enum BusinessExceptionCode {

    USER_LOGIN_NAME_EXIST("登录名已存在"),
    ;

    private String desc;

    BusinessExceptionCode(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

异常code是通过枚举来定义的,接下来这个异常类的内容为:

package com.cexo.wiki.exception;

public class BusinessException extends RuntimeException{

    private BusinessExceptionCode code;

    public BusinessException (BusinessExceptionCode code) {
        super(code.getDesc());
        this.code = code;
    }

    public BusinessExceptionCode getCode() {
        return code;
    }

    public void setCode(BusinessExceptionCode code) {
        this.code = code;
    }

    /**
     * 不写入堆栈信息,提高性能
     */
    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

其中:

它的意思是不抛出一堆的异常堆栈信息了,因为这个属于业务异常,并非系统异常,通过业务异常的code其实就知道此异常的问题,它是纯业务逻辑,并非是因为程序的缺陷,所以这里就重写一下fillInStackTrace()。

3、抛出异常:

接下来咱们就可以在用户名重复的处理处这样来抛出业务异常了:

4、统一异常处理:

还记得在之前Spring Boot + Vue3 前后端分离 实战 wiki 知识库系统<六>--电子书管理功能开发二使用过SpingBoot的全局异常处理么?对于这个自定义的异常处理也类似,处理如下:

5、运行:

接下来咱们运行看一下效果:

6、编辑用户名问题:

还有一个细节需要再说明一下,就是编辑用户时,是不允许编辑用户名的,目前控制它的地方在这:

也就是当用户的id不为空,说明是编辑操作,此时登陆名是只读的不允许编辑,而当用户的id为空,则说明是新增操作,当然登录名是可写入的,另外目前密码不可以编辑,可以把这个条件暂且先去掉,因为接下来就要对密码进行进一步处理,目前密码名文存在库中肯定是不合理的:

此时编辑的时候就可以编辑密码了:

这个不是说的重点,重点是它:

为啥要加“!!”两个叹号呢?那将它去掉看会有什么影响就知道了:

此时就可以使用“!!”来绕过语法检查,这个算是一个小技巧。

另外目前貌似前端用户名已经禁用输入来防止更改用户名已经完美了,但是!!!对于前端的东东用户都可以绕过去的,最典型的是通过浏览器的调试来绕过,如下:

所以有必要在后端针对这种从前端绕过去来修改用户名的情况进行一下处理,那如何处理呢?有一种简单的改法,就是在后端忽略用户名的更新既可,也就是不管前端针对用户名做何等操作,都忽略,具体可以这样来做:

其实它有另一个方法可以满足咱们目前的场景:

而它的功能其实跟进去看一下它的sql定义就知道了:

也就是只有有值的情况下才会更新,为空则就不会更新了,那么思路来了,我们在调它之前,主动将loginName给置空不就行了,如下:

好,此时再运行看一下效果:

完美解决。

关于密码的两层加密处理:

概述:

接下来咱们来处理密码加密的问题了,如上面也提到过,目前咱们的用户名的密码都是明文存储的:

这是一个非常危险的事情,假如数据被泄漏了,所有用户的密码也就知道了,所以必须得加密存储,下面来处理下。

密码加密存储:

修改也比较简单,在后端保存的时候,使用springframework的md5进行一下加密既可,如下:

此时运行看一下:

此时看一下库里的密码是否已经加密了:

但是!!!现在加密之后在前端编辑时就会有一些问题了,每次编辑,如果不想改密码只改昵称,貌似密码也每次都会变:

关于这个问题之后再来解决,目前先解决保存时加密的问题。 

密码加密传输:

看似目前编辑加密没问题,其实在前端这块传输还是有问题的,这里看一下:

发现问题了么?前端的密码其实还是明文的,那当然也得加密喽,其加密方式也是用MD5,下面来实现一下。

1、拷贝一个md5加密的js:

而此md5的文件地址为:https://blog-static.cnblogs.com/files/webor2006/md5.js?t=1691852396&download=true,其中我们需要调用的函数就是:

2、引入到页面中:

接下来咱们就可以将这个js引入到用户管理的页面中了,其引入方法如下:

然后在用户管理页面在保存时对用户的密码进行一下加密处理:

加入这么一句:

但是!!!报错了呀,其中hexMd5就是调用我们新引入的md5.js中的函数,因为它是全局的,哪个页面都可以调用,而KEY也是定义在md5.js中的:

这个叫“盐值”,俗称的“加盐”,为啥要加一个盐值呢?关于这块可以网上搜一下,比如密码“123”,如果不加盐值,它的md5是固定的,根据md5可能很容易知道它是"123",但是如果这个密码加了一串特殊字符再进行md5,此时用户就很难逆推出来原密了。好,还是回到解决这个报错问题上来,其实是因为我们页面上使用的typescript,它默认是无法直接识别javascript的这个方法和变量,需要这样声明一下就可以了:

此时咱们再来运行看一下:

这就是密码的两层加密,一层是前端的md5,另一层是后端的md5。

增加重置密码功能:

修改用户时,不能修改密码:

对于加了密的密码,在编辑时是不应该再让用户能进行修改的,毕竟加了密的密文再编辑是没有意义的,所以这里还是在编辑时将密码表单栏隐藏,但是在新增用户时是需要显示的,如下:

与v-show功能类似的还有一个v-if,关于这俩的区别其实在之前的学习中也提到过了,可以参考它:Spring Boot + Vue3前后端分离实战wiki知识库系统<十>--文档管理功能开发二,运行看一下:

同样的,对于后端也需要做一下处理,避免绕过前端能对密码进行修改,如下:

单独开发重置密码表单和接口:

概述:

对于用户的密码有可能会有忘记的情况,此时就应该有一个重置用户密码的功能。

1、准备接口:

这里先来准备重置的接口,如下:

其中UserResetPasswordReq新建了一个,因为它里面的入参只需要一个密码既可,跟用户保存的入参是不一样的:

package com.cexo.wiki.req;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

public class UserResetPasswordReq {
    private Long id;

    @NotNull(message = "【密码】不能为空")
    // @Length(min = 6, max = 20, message = "【密码】6~20位")
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】至少包含 数字和英文,长度6-32")
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", password=").append(password);
        sb.append("]");
        return sb.toString();
    }
}

接下来再来实现service层的代码:

2、准备重置入口:

先在列表操作按钮上新增一个重置:

此时运行看一下:

其中点击事件定义如下:

3、实现重置:

接下来则来实现重置的功能。

1、准备重置的模态框:

这个模态框可以copy至编辑时的模态框,只是表单内容不一样,比较简单,这里细节就略过了,直接贴相关的代码:

代码:

 <a-modal
      title="重置密码"
      v-model:visible="resetModalVisible"
      :confirm-loading="resetModalLoading"
      @ok="handleResetModalOk"
  >
    <a-form :model="user" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
      <a-form-item label="新密码">
        <a-input v-model:value="user.password"/>
      </a-form-item>
    </a-form>
  </a-modal>

然后定义相关的变量及函数实现,这块也没有任何新的技术点,也直接贴出来了:

代码:

// -------- 重置密码 ---------
    const resetModalVisible = ref(false);
    const resetModalLoading = ref(false);
    const handleResetModalOk = () => {
      resetModalLoading.value = true;

      user.value.password = hexMd5(user.value.password + KEY);

      axios.post("/user/reset-password", user.value).then((response) => {
        resetModalLoading.value = false;
        const data = response.data;
        if (data.success) {
          resetModalVisible.value = false;

          // 重新加载列表
          handleQuery({
            page: pagination.value.current,
            size: pagination.value.pageSize,
          });
        } else {
          message.error(data.message);
        }
      });
    };

2、重置密码点击事件处理:

接下来则来实现密码重置点击事件的逻辑,也就是将咱们准备的重置模态框给展示出来:

4、运行:

接下来咱们运行看一下效果:

可以看到,我新建了一个账号,将密码设置成123之后,跟test2这个用户密码也是123最后生成的md5是同一个值:

证明密码重置之后的密码是好使的,当然最终得要实现了登录功能再来进行这块密码修改功能是否一切正常,用户登录模块后续做到时再来验证。

单点登录token与JWT介绍:

目前为止,咱们已经将用户的管理功能实现了,那接下来就可以来进行用户登录功能的开发了,这里在开发之前,先理论化了解登录的一些概念,刚好也篇末了,埋个伏笔。

登录流程:

这里先来了解一下通常登录的整个流程,总体分为两大块:登录和校验。

登录:

1、前端输入用户名和密码。

2、校验用户名和密码。(包含基本的用户名和密码的格式校验、用户名和密码是否匹配校验)。

3、生成token,也称令牌、登录标识,其实也就是一串“唯一”的字符串(既使是同一个用户,登录多次,每次的token也是不一样的)。

4、后端保存token(最终会保存到redis中)。

5、前端保存token。

校验:

1、前端请求时,带上token,但并非所有的接口都需要校验,一般就是管理类的接口(增删改)是需要校验token的。(通常是token是放在header请求头里)

2、登录拦截器,校验token。(到redis获取token) 

3、校验成功则继续后面的业务。

4、校验失败则跳回到登录界面。

单点登录系统:【了解】

在上面的登录流程中可以看到其流程还是很多的, 如果有很多系统都需要来自己实现一遍,那成本比较高,也不好维护,所以可以将它做成一个登录系统,以后所有产品需要登录功能都跳到这个系统里,当然做法有两种:一种是该系统已经带登录界面及接口,第二种是各个产品自己维护登录界面,这套系统只维护登录相关的接口。而通常这套系统包含如下功能:用户管理、登录、登录校验、退出登录,简单了解一下,不同公司对于它的定义也不一样。

token与JWT:【了解】

再来了解一下概念,对于token+redis,其实这个token,只要保证它唯一,可以用md5字符串、时间戳等,它的特点就是其值是无意义的,因为它不能代表任何业务意义;但是对于JWT就不一样了,说到不一样当然它的token是有意义的喽,是的,因为它是将用户的业务信息通过加密手段而生成的token,所以通过token就可以解出来用户的信息,说了这么多,先度娘一下JWT,在这篇什么是JWT?(细致讲解)_Ethereal@_@的博客-CSDN博客文章里是这么说明的:

也一知半解,它其实有一个官网JSON Web Tokens - jwt.io,打开了解一下:

哦,原来JWT的全称是JSON Web Tokens,官网这句描述其实也不知道它是干嘛的,往下翻,官网直接给出了一个在线的效果:

其中加密的算法有很多种,可以根据实际情况来选择:

也就是这个token信息是有意义的,通过这个token是可以解密的,而要使用它,则需要添加如下依赖:

而核心使用就是这两个:

这里仅当做个知识了解,待未来真正使用到它时再进一步了解,下篇就正式进入用户登录功能的开发,这篇先这样了。

  关注个人公众号,获得实时推送

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/872968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

谈谈语音助手

目录 1.什么是语音助手 2.语音助手的发展过程 3.现在有哪些成熟的语音助手 4.语音助手对人类发展的影响 1.什么是语音助手 语音助手是一种能够通过语音交互与用户进行沟通和执行任务的虚拟助手。它基于人工智能和自然语言处理技术&#xff0c;能够理解用户的语音指令&#x…

LeetCode 36题:有效的数独

题目 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff…

MEC | 条款4 非必要不提供 default constructor

条款4 非必要不提供 default constructor 文章目录 条款4 非必要不提供 default constructorclasses 必须有默认构造函数&#xff1f;Example1. 产生数组解决方法 2.不适用于 template-based container clases3.虚基函数 探讨>>>>> 欢迎关注公众号【三戒纪元】 …

CANoe自动化工程的搭建

基于XMLCAPL建立自动化工程 1、导入ini文件2、新建 Test Environment3、报告类型4、代码编写 1、导入ini文件 工程的配置的文件&#xff0c;配置DUT相关信息&#xff0c;具体视工程而编写内容。 2、新建 Test Environment 1、新建XML测试用例环境 2、导入XML测试用例文件 …

vulnhub靶机Deathnote

难度&#xff1a;easy 下载地址&#xff1a;https://download.vulnhub.com/deathnote/Deathnote.ova 主机发现 arp-scan -l 端口扫描 nmap --min-rate 10000 -p- 192.168.21.140 进一步查看目标的端口的服务和版本 nmap -sV -sT -O -p22,80 192.168.21.140 扫描端口的漏洞…

微服务实战项目-学成在线-项目优化(redis缓存优化)

微服务实战项目-学成在线-项目优化(redis缓存优化) 1 优化需求 视频播放页面用户未登录也可以访问&#xff0c;当用户观看试学课程时需要请求服务端查询数据&#xff0c;接口如下&#xff1a; 1、根据课程id查询课程信息。 2、根据文件id查询视频信息。 这些接口在用户未认…

Java面试——一分钟搞懂限流算法

为什么限流 运营网站&#xff0c;经常会遇到各种挑战&#xff1a;某黑客发起DoS攻击、网络爬虫网页抓取、商品秒杀活动、双十一与618等场景&#xff0c;会使流量突然激增&#xff0c;如果不限制流量的访问就会使系统宕机。 常见的限流算法 1.漏桶算法&#xff08; LEAKY BUC…

Eclipse-配置彩色输出打印

文章目录 前言配置下载查看是否安装 前言 这是一篇古老的文章&#xff0c;那个时候还在用Eclipse &#xff0c;现在已经换 IDEA 了… 这是一篇 2018 年的文章&#xff0c;我只是将文章从个人比较挪到了CSDN 中 配置 配置完然后下载下面插件即可生成彩色代码。 下载 ANSI …

Vue2-收集表单数据、过滤器、内置指令与自定义指令、Vue生命周期

&#x1f954;&#xff1a;我徒越万重山 千帆过 万木自逢春 更多Vue知识请点击——Vue.js VUE2-Day4 收集表单数据1、不同标签的value属性2、v-model的三个修饰符 过滤器内置指令与自定义指令1、内置指令2、自定义指令定义语法&#xff08;1&#xff09;函数式&#xff08;2&am…

VMware vCenter 6.5 断电后无法启动修复方案

目录 第1章 前言 第2章 问题现象 第3章 解决方案 第4章、Tips 第1章 前言 本文主要介绍VMware vcsa 6.5由于电源异常/存储异常之后无法启动&#xff0c;进入磁盘自检模式处理方法。&#xff08;最近遇到类似情况比较多&#xff0c;Citrix Xenserver也遇到了&#xff0c;后来…

rocketMq启动broker报错找不到或无法加载主类 Files\Java\jdk1.8.0_171\lib\dt.jar;C:\Program]

假如弹出提示框提示‘错误: 找不到或无法加载主类 xxxxxx’。 1.打开runbroker.cmd 将"%CLASSPATH%"加上英文双引号&#xff0c;切勿别加中文双引号 2.打开runserver.cmd 同理 将"%CLASSPATH%"加上英文双引号&#xff0c;切勿别加中文双引号 3.正常执行即…

【Linux】邮件服务器搭建 postfix+dovecot+mysql (终极版 超详细 亲测多遍无问题)

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录 前言基础原理准备工作一 、安装关于权…

一.RocketMQ概念

RocketMQ概念 1.概念2.应用场景3.MQ的优点和缺点4.常见MQ对比 1.概念 MQ(Message Queue)&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件&#xff0c;是一套提供了消息生产、存储、消费全过程API的软件系统。 RocketMQ是阿里巴巴2016年MQ中间件&…

C++QT教程3——手册4.11.1自带教程(笔记)——创建一个QT快速应用

文章目录 创建一个QT快速应用创建项目创建主视图添加应用逻辑为视图添加动画素材文件 参考文章 创建一个QT快速应用 本教程使用内置的QML类型&#xff0c;介绍了Qt Quick的基本概念。有关可以选择的用户界面选项的更多信息&#xff0c;请参阅用户界面。 本教程描述了如何使用…

约束综合中的逻辑互斥时钟(Logically Exclusive Clocks)

注&#xff1a;本文翻译自Constraining Logically Exclusive Clocks in Synthesis 逻辑互斥时钟的定义 逻辑互斥时钟是指设计中活跃&#xff08;activate&#xff09;但不彼此影响的时钟。常见的情况是&#xff0c;两个时钟作为一个多路选择器的输入&#xff0c;并根据sel信号…

【人工智能前沿弄潮】——生成式AI系列:Diffusers应用 (2) 训练扩散模型(无条件图像生成)

无条件图像生成是扩散模型的一种流行应用&#xff0c;它生成的图像看起来像用于训练的数据集中的图像。与文本或图像到图像模型不同&#xff0c;无条件图像生成不依赖于任何文本或图像。它只生成与其训练数据分布相似的图像。通常&#xff0c;通过在特定数据集上微调预训练模型…

Idea报错:Cannot resolve symbol “springframework“以及各种依赖包

问题描述&#xff1a; Idea导入了maven项目之后出现报错Cannot resolve symbol “springframework” &#xff0c;识别不了这个标识或者找不到这个包&#xff0c;明明这些依赖和包都有就是出现报错&#xff0c;并且运行按钮变成灰色 解决办法&#xff1a; 其实这个原因大概率就…

React构建的JS优化思路

背景 之前个人博客搭建时&#xff0c;发现页面加载要5s才能完成并显示 问题 React生成的JS有1.4M&#xff0c;对于个人博客服务器的带宽来说&#xff0c;压力较大&#xff0c;因此耗费了5S的时间 优化思路 解决React生成的JS大小&#xff0c;因为我用的是react-router-dom…

从源码Debug深入spring事件机制,基于观察者模式仿写spring事件监听骨架

文章目录 1.测试案例2.DEBUG源码分析3. 异步监听4.ApplicationListener子接口5. 注解支持6. 基于观察者模式高仿spring事件监听6.1 先定义自定义一个事件6.2 定义两个监听器6.3 定义一个持有所有监听器的对象&#xff0c;类似spring的SimpleApplicationEventMulticaster6.4 事件…

全面解析 Axios 请求库的基本使用方法

Axios 是一个流行的基于 Promise 的 HTTP 请求库&#xff0c;用于在浏览器和 Node.js 中进行 HTTP 请求。它提供了简单易用的 API&#xff0c;可以发送各种类型的请求&#xff08;如 GET、POST、PUT、DELETE等&#xff09;&#xff0c;并处理响应数据&#xff0c;Axios 在前端工…