4639 字
23 分钟
梦潭伙伴匹配项目总结
2023-08-17

梦潭伙伴匹配项目总结#

第一部分#

  1. 使用vite进行项目的初始化,node版本需要与其匹配,可以下载nvm来进行node版本的管理。

    yarn create vite
    

    image-20240124122542894

    image-20240124122706606

  2. 使用vant进行前端页面的布局不限于导航栏,tab栏的搭建。

    image-20240124122904208

  3. 设计数据库表,分析需要哪些字段。

    image-20240124125441196

  4. 用户表的添加tag字段

    1. 关联表:
      1. 优点:查询灵活,可以正查,反查
      2. 缺点:需要多建一个表,多维护一个表
      3. 尽量减少关联查询。
    2. 往用户表中添加json字符串来补充tag字段。
      1. 优点:查询方便,不用新建表,标签是固有属性节省开发成本。
      2. 如果性能低可以用缓存。
    3. 具体情况根据需求具体分析,选择合适的。
  5. 开发后端接口

    • 搜索标签

      1. 允许用户传入多个标签,多个标签都存在才搜索出来,and。代码举例:like ‘%Java%’ and like ‘%C++%’。
      2. 允许用户传入多个标签,有任何一个标签存在就能搜索出来 or。代码举例:like ‘%Java%’ or like ‘%C++%’。
    • 两种方式

      1. SQL查询(实现简单,可以通过拆分查询进一步优化)
      2. 内存查询(灵活,可以通过并发进一步优化)
    • 选择场景

      1. 如果参数可以分析,根据用户的参数去选择查询方式,比如标签数。
      2. 如果参数不可分析,并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回用谁。
      3. 还可以SQL查询与内存计算相结合,比如先用SQL过滤掉部分Tag。
    • 并行流和串行流

      1. 串行流 Stream ,并行流 parallelStream
      2. 并行流parallelStream缺点:parallelStream使用公共线程池,如果某一个方法耗时特别长,那么慢慢的整个线程池会都交给该方法,就没有多余的线程分配给其他任务了。
    • tag的判空

      • Optional可选类,可以减少判断的分支

      • tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
        
  6. 解析JSON字符串

    1. 名词解释

      • 序列化:java对象转换成json
      • 反序列化:把json转为java对象
    2. java和json序列化库举例:

      • 标签的string转json,利用gson库实现

      • Set<String> tempTagNameSet = gson.fromJson(tagsStr, new TypeToken<Set<User>>(){}.getType());
        

第二部分#

  1. 前端整合路由

    • vue-router引入

      官方文档

    • main.ts

      import {createApp} from 'vue'
      import {Button, Icon, NavBar, Tabbar, TabbarItem} from 'vant';
      import App from './App.vue'
      import * as VueRouter from 'vue-router'
      import routes from "./config/route.ts";
      
      const app = createApp(App);
      
      app.use(Button);
      app.use(NavBar);
      app.use(Tabbar);
      app.use(TabbarItem);
      app.use(Icon)
      
      const router = VueRouter.createRouter({
          history: VueRouter.createWebHashHistory(),
          routes,
      })
      
      app.use(router)
      
      app.mount('#app')
      
    • route.ts

      import Index from "../pages/Index.vue";
      import Team from "../pages/Team.vue";
      
      const routes = [
          { path: '/', component: Index },
          { path: '/team', component: Team },
          { path: '/user', component: Team },
      ]
      
      export default routes;
      
    • BasicLayout.vue

      <template>
        <van-nav-bar title="标题" left-text="返回" left-arrow>
          <template #right>
            <van-icon name="search" size="18"/>
          </template>
        </van-nav-bar>
        <div id="content">
          <router-view/>
        </div>
        <van-tabbar route @change="onChange">
          <van-tabbar-item to="/" icon="home-o" name="index">主页</van-tabbar-item>
          <van-tabbar-item to="/team" icon="search" name="team">队伍</van-tabbar-item>
          <van-tabbar-item to="/user" icon="friends-o" name="user">个人</van-tabbar-item>
        </van-tabbar>
      </template>
      
  2. 搜索页面开发

    • flat和flatmap

      JavaScript 数组展平方法: flat() 和 flatMap()

    • 搜索的方法,展平tagList标签列表(逻辑有问题,仅用于相关方法学习)

      const onSearch = () => {
        activeIds.value = tagList
          .flatMap((parentTag) => parentTag.children)
          .filter((item) => item.text.contain(searchText.value));
      };
      
    • 同上,正确方法

      //标签列表
      const originTagList = [
        {
          text: "浙江",
          children: [
            { text: "杭州", id: "杭州" },
            { text: "温州", id: "温州" },
          ],
        },
        {
          text: "江苏",
          children: [
            { text: "南京", id: "南京" },
            { text: "无锡", id: "无锡" },
            { text: "徐州", id: "徐州" },
          ],
        },
      ];
      
      let tagList = ref(originTagList);
      
      const onSearch = () => {
        tagList.value = originTagList.map((parentTag) => {
          const tempChildren = [...parentTag.children];
          const tempParentTag = { ...parentTag };
          tempParentTag.children = tempChildren.filter((item) =>
            item.text.includes(searchText.value),
          );
          return tempParentTag;
        });
      };
      //清空搜索框
      const onCancel = () => {
        searchText.value = "";
        tagList.value = originTagList;
      };
      
  3. 个人信息页面

    • 定义user的用户类型

      /**
       * 用户类型
       */
      export type userType = {
        id: number;
        username: string;
        userAccount: string;
        avatarUrl?: string;
        gender: number;
        phone: string;
        email: string;
        userStatus: number;
        userRole: number;
        fantasyCode: string;
        tags: string[];
        createTime: Date;
      };
      
    • 编写前端页面

  4. 用户编辑页面

    • 定义一个传三个参数的点击事件

      @click="toEdit('avatarUrl', '头像', user.avatarUrl)"
      
    • 具体方法

      const toEdit = (editKey: string, editName: string, currentValue: string) => {
        router.push({
          path: "/user/edit",
          query: {
            editKey,
            editName,
            currentValue,
          },
        });
      };
      
    • 用户编辑页面定义传递过来的参数

      const route = useRoute();
      const editUser = ref({
        editKey: route.query.editKey,
        editName: route.query.editName,
        currentValue: route.query.currentValue,
      });
      
    • 采用模板动态展示

      <van-form @submit="onSubmit">
        <van-cell-group inset>
          <van-field
            v-model="editUser.currentValue"
            :name="editUser.editKey"
            :label="editUser.editName"
            :placeholder="`请输入${editUser.editName}`"
          />
        </van-cell-group>
        <div style="margin: 16px">
          <van-button round block type="primary" native-type="submit">
            提交
          </van-button>
        </div>
      </van-form>
      
  5. 页面展示

    • 个人信息

      image-20240602172541352

    • 修改信息

      image-20240602172622972

    • 搜索

      image-20240602172722742

第三部分#

  1. 后端整合接口文档

    • 什么是接口文档?

      • 写接口信息的文档,每条信息包括
        • 请求参数
        • 相应参数
          • 错误码
        • 接口地址
        • 接口名称
        • 请求类型
        • 请求格式
        • 备注
      • 谁用接口文档?一般是后端或者负责人提供,后端前端都要使用。
    • 为什么需要接口文档

      • 有个书面内容(背书或者归档),便于大家参考和查阅,便于沉淀和维护,拒绝口口相传
      • 接口文档便于前端和后端开发对接,前后端联调介质。后端=>接口文档<=前端
      • 好的接口文档支持在线调试、在线测试,可以作为工具提高我们的测试开发效率。
    • 如何做接口文档

      • 手写(比如腾讯文档,Markdown笔记)
      • 自动化接口文档生成:自动根据项目代码生成完整的文档或在线调试的网页。Swagger、Postman(侧重接口管理);apifox、apipost、eolink(国产)
    • Swagger原理

      1. 引入依赖

      2. 自定义Swagger配置类

      3. 定义需要生成接口文档的代码位置(Controller)

        注:线上环境不要把接口暴露出去。

      4. 可以通过在controller方法上添加@Api、@ApiImplicitParam(name=“name”,value=“姓名”,require=true)、@ApiOperation(value=“向客人问好”)等注解来自定义生成接口描述信息。

      5. 如果springboot version>=2.6,需要添加如下配置:

        mvc:
          pathmatch:
            matching-strategy: ant_path_matcher
        
  2. 看上了网页信息怎么抓取到

    • 分析网站是如何获取这些信息的,哪个接口?

      curl "https://api.zsxq.com/v2/hashtags/28855251481421/topics?count=20" ^
        -H "accept: application/json, text/plain, */*" ^
        -H "accept-language: zh-CN,zh;q=0.9" ^
        -H "origin: https://wx.zsxq.com" ^
        -H "priority: u=1, i" ^
        -H "referer: https://wx.zsxq.com/" ^
        -H ^"sec-ch-ua: ^\^"Google Chrome^\^";v=^\^"125^\^", ^\^"Chromium^\^";v=^\^"125^\^", ^\^"Not.A/Brand^\^";v=^\^"24^\^"^" ^
        -H "sec-ch-ua-mobile: ?0" ^
        -H ^"sec-ch-ua-platform: ^\^"Windows^\^"^" ^
        -H "sec-fetch-dest: empty" ^
        -H "sec-fetch-mode: cors" ^
        -H "sec-fetch-site: same-site" ^
        -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" ^
      
    • 用程序去调用接口(JAVA/Python)。

    • 处理(清洗)一下数据,就可以写入数据库。

  3. 流程

    • 从excal导入用户数据,判重。easy excal
    • 抓取写了自我介绍的同学的信息,提取出用户昵称,用户唯一id自我介绍信息‘。
    • 从自我介绍中提取信息,写入数据库中。
  4. EasyExcal

    两种读取方式

    • 确定表头:建立对象和表格形成映射。
    • 不确定表头:每一行的数据映射为Map<String,Object>

第四部分#

  1. 前端开发

    • 前端页面跳转传值
      1. query => url searchParams,url后附加参数,传递的值长度有限。
      2. vuex(全局状态管理),搜索页将关键词塞到状态中,搜索结果页从状态取值。
      image-20250106114729580
  2. 后端开发

    • @GetMapping("/search/tags")
      public  BaseResponse<List<User>> searchUserByTags(@RequestParam(required = false) List<String> tagNameList){
          if (CollectionUtils.isEmpty(tagNameList)){
              throw new BusinessException(ErrorCode.PARAMS_ERROR);
          }
          List<User> userList = userService.searchUsersByTags(tagNameList);
          return ResultUtils.success(userList);
      }
      
  3. 前后端联调

    • 引入axios

      • import axios from "axios";
        
        const myAxios = axios.create({
            baseURL: 'http://localhost:8080/api',
        });
        
        // 添加请求拦截器
        myAxios.interceptors.request.use(function (config) {
            // 在发送请求之前做些什么
            console.log("我要发请求了",config)
            return config;
        }, function (error) {
            // 对请求错误做些什么
            console.log("出现错误",error)
            return Promise.reject(error);
        });
        
        // 添加响应拦截器
        myAxios.interceptors.response.use(function (response) {
            // 2xx 范围内的状态码都会触发该函数。
            // 对响应数据做点什么
            console.log("我要响应了",response)
            return response;
        }, function (error) {
            // 超出 2xx 范围的状态码都会触发该函数。
            // 对响应错误做点什么
            return Promise.reject(error);
        });
        
        export default myAxios
        
    • 使用钩子onMounted进行相应的配置

      • onMounted(async () => {
          const userListData = await myAxios
            .get("/user/search/tags", {
              params: {
                tagNameList: tags,
              },
              paramsSerializer: (params) => {
                return qs.stringify(params, { indices: false });
              },
            })
            .then(function (response) {
              console.log("/user/search/tags succeed 请求成功", response);
              showSuccessToast("请求成功");
              return response.data?.data;
            })
            .catch(function (error) {
              console.error("/user/search/tags error 请求失败", error);
              showFailToast("请求失败");
            });
          if (userListData) {
            userListData.forEach((user) => {
              if (user.tags) {
                user.tags = JSON.parse(user.tags);
              }
            });
            userList.value = userListData;
          }
        });
        
    • 注意使用qs对传值进行序列化

    • 对tags进行json数组的转换

    • 后端跨域配置添加注解

      @CrossOrigin(origins = {"http://localhost:5173"})
      

第五部分#

  1. 用户登录(session共享分布式登录)

    • 种session的时候注意范围,可以在servlet下配置cookie.domain,如果要共享cookie,可以种一个更高层的公共域名

    • 多服务器时登录解决方案

      image-20250108143909802

    • 如何做?

      1. Redis(基于内存的K/V数据库)选择Redis,因为用户信息读取/是否登录的判断极其频繁,Redis基于内存,读写性能高,简单的单机数据 qps 5w~10w
      2. MySQL
      3. 文件服务器ceph
    • Redis安装

      1. 下载windows版本redis

      2. redis管理工具 quick redis:https://quick123.net/

      3. 引入redis,注意版本号对应

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.13</version>
        </dependency>
        
      4. 引入spring-session和redis的整合,使得自动将session存储到redis中:

        <!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>2.6.4</version>
        </dependency>
        
      5. 修改spring-session存储配置spring.session.store-type

        默认是none,表示存储在单台服务器

        store-type:redis,表示从redis读写session

  2. 后端开发

    • 用户更新信息的controller层方法

      @PostMapping("/update")
      public BaseResponse<Integer> updateUser(@RequestBody User user, HttpServletRequest request) {
          //判断是否为空
          if (user == null) {
              throw new BusinessException(ErrorCode.PARAMS_ERROR);
          }
          User loginUser = userService.getLoginUser(request);
          //判断权限,触发更新
          Integer result = userService.updateUser(user, loginUser);
          return ResultUtils.success(result);
      }
      
    • 使用JSON的请求方式时,Post请求的变量需要加上@RequestBody。

    • 优化service层方法,新增getLoginUser()以及updateUser()两个方法,将判断是否为管理员的isAdmin()方法放入service层,使得多个地方可以复用。

      @Override
      public int updateUser(User user, User loginUser) {
          long userId = user.getId();
          if (userId <= 0) {
              throw new BusinessException(ErrorCode.PARAMS_ERROR);
          }
          //如果是管理员,更新任意用户
          //如果不是管理员,只能更新自己
          if (!isAdmin(loginUser) && userId != loginUser.getId()) {
              throw new BusinessException(ErrorCode.NO_AUTH);
          }
          User oldUser = userMapper.selectById(userId);
          if (oldUser == null) {
              throw new BusinessException(ErrorCode.NULL_ERROR);
          }
          return userMapper.updateById(user);
      }
      
  3. 前端开发

    • 利用自封装的axios更新用户修改页面

      const onSubmit = async () => {
        const res = await myAxios.post('/user/update', {
          'id':1,
          [editUser.value.editKey as string]: editUser.value.currentValue,
        })
        if (res.code===0 && res.data>0){
          showSuccessToast("修改成功");
          router.back();
        }else {
          showFailToast("修改失败");
        }
      };
      
    • 完善之前开发未完成的登录部分

      image-20250109161527687

    • 用户个人信息部分

      onMounted(async () => {
        const res = await myAxios.get("/user/current");
        if (res.code === 0) {
          console.log(res)
          user.value = res.data.data;
        }else{
          showFailToast("获取失败");
        }
      });
      
  4. 前后端联调

    • 登录时前端请求要携带cookie,后端需要允许跨域cookie的携带

      • 在自己的axios配置中添加

        //携带cookie
        myAxios.defaults.withCredentials = true;
        
      • 在后端的controller层前配置@CrossOrigin,其中添加如下配置

        @CrossOrigin(origins = {"http://localhost:5173"},allowCredentials = "true")
        
    • 将获取当前登录用户进行封装

      import { getCurrentUserState, setCurrentUserState } from "../states/user.ts";
      import myAxios from "../plugins/myAxios.ts";
      
      export const getCurrentUser = async () => {
        // const currentUser = getCurrentUserState();
        // if (currentUser){
        //   return currentUser;
        // }
        const res = await myAxios.get("/user/current");
          if (res.code === 0) {
            setCurrentUserState(res.data);
            return res.data;
          }
          return null;
      };
      
    • 对用户登录状态进行缓存

      import { userType } from "../models/user";
      
      let currentUser: userType;
      
      const setCurrentUserState = (user: userType) => {
        currentUser = user;
      };
      
      const getCurrentUserState = () => {
        return currentUser;
      };
      
      export { setCurrentUserState, getCurrentUserState };
      

第六部分#

  1. 主页开发

    • 通过list列表,将默认推荐信息展示在主页。

    • 抽象列表组件,方便复用

      <template>
        <van-card
          v-for="user in props.userList"
          :desc="user.profile"
          :title="`${user.username}(${user.fantasyCode})`"
          :thumb="user.avatarUrl"
        >
          <template #tags>
            <van-tag
              plain
              type="primary"
              v-for="tag in user.tags"
              style="margin-right: 5px; margin-top: 5px"
              >{{ tag }}
            </van-tag>
          </template>
          <template #footer>
            <van-button size="small">联系我</van-button>
          </template>
        </van-card>
      </template>
      
      <script setup lang="ts">
      import { userType } from "../models/user";
      
      interface UserCardListProps {
        userList: userType[];
      }
      
      const props = withDefaults(defineProps<UserCardListProps>(), {
        userList: [],
      });
      </script>
      
      <style scoped></style>
      
  2. 批量导入数据

    1. 用可视化界面:适合一次性导入,数据量可控
    2. 写程序:for循环,建议分批,不要一把梭哈(用接口控制)
    3. 执行SQL语句:适用于小数据量
  3. 编写一次性任务

    • 问题
      1. 建立和释放数据库链接(批量查询)
      2. for循环是绝对线性的

第七部分#

  1. 数据查询慢怎么办:提前把数据取出来保存好(通常保存到读写更快的介质,比如内存),就可以更快的读写。

  2. 缓存的实现

    • Redis(分布式缓存)

    • memcached(分布式)

    • Etcd(云原生架构的一个分布式存储)


    • ehcache(单机)

    • 本地缓存(Java内存Map)

    • Caffeine(Java内存缓存,高性能)

    • Google Guava

  3. Redis

    • 键值对存储系统

    • Redis数据结构

      1. String字符串类型:name:“tefantasy”

      2. List列表:name[“tefantasy”,“MY”,“tefantasy”]

      3. Set集合:name[“tefantasy”,“MY”](值不能重复)

      4. Hash哈希:nameAge{“tefantasy”:1,“MY”:2}

      5. Zset集合:names:{tefantasy-9,MY-12}


      • 高级数据结构

      1. bloomfilter(布隆过滤器,主要从大量数据中快速过滤值,比如邮件黑名单拦截)
      2. geo(计算地理位置)
      3. hyperloglog(大数据统计)
      4. pub/sub(发布订阅,类似消息队列)
      5. BitMap(把数据按照010101类似的方式存储)
    • 自定义RedisTemplate

      Snipaste_2025-01-18_19-15-28

      注:引入一个库时,先编写测试类。

  4. 设计缓存Key

    • 不同用户看到的数据不同
    • systemId:moduleId:func(不要和别人冲突)
    • mengtan:user:recommend
    • redis内存不能无限叠加,一定要设置过期时间
  5. 缓存预热

    • 缓存预热优点:可以让用户始终访问很快
    • 缺点:
      1. 增加开发成本(需要额外的开发设计)
      2. 预热的时机和时间如果错了,有可能你缓存的数据不对或者太老
      3. 需要占用额外的空间
    • 如何缓存预热
      1. 定时
      2. 模拟触发(手动触发)
    • 注:分析优缺点的时候,要打开思路,从整个项目从0到1的链路上去分析。
    • 实现
      • 用定时任务,每天刷新所有用户的推荐列表
      • 注意点:
        1. 缓存预热的意义(新增少,总用户多)
        2. 缓存的空间不能太大,要预留给其他缓存空间
        3. 缓存数据的周期(此处每天一次)
  6. 定时任务实现

    • Spring Scheduler(springboot默认整合了)

    • Quartz(独立于Spring存在的定时任务框架)

    • XXL-Job之类的分布式任务调度平台(界面+SDK)


    • 第一种方式:
      1. 主类开启@EnableScheduling
      2. 给要定时执行的方法添加@Scheduling注解,指定cron表达式或者执行频率
  7. JAVA里的实现方式

    • Spring Data Redis

      • 引入

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.13</version>
        </dependency>
        
      • 配置Redis地址

        #redis配置
        redis:
          port: 6379
          host: localhost
          database: 0
        
  • Jedis

    • 独立于spring操作redis的Java客户端
  • Lettuce

    • 高阶的操作redis的java客户端
    • 异步,支持连接池
  • Redission

    • 分布式的操作redis的java客户端,让你像使用本地集合一样使用redis
  • 对比

    • image-20250119130643110
  1. 控制定时任务的执行

    • 如何做:

      1. 分离定时任务和主程序,只在一个服务器运行定时任务,成本太大

      2. 写死配置,每个服务器都执行定时任务,只有固定ip可以继续执行下去,成本小,但是ip可能不是固定的,写的太死了

      3. 动态配置,配置是可以轻松的、很方便的更新(代码无需重启),但是只有ip符合配置条件的才真实执行业务逻辑

        • 数据库

        • Redis

        • 配置中心(Nacos、Apollo、spring cloud config)

        • 问题:服务器多了,ip还是不可控很麻烦,需要人工修改。

      4. 分布式锁:只有抢到锁的服务器才能执行业务逻辑。坏处:增加成本。好处:不用手动配置,多少个服务器都一样

    • 有限资源的情况下,控制同一时间(段)只有某些线程(用户/服务器)才能访问到资源。

    • Java实现锁:synchronized关键字、并发包的类

    • 问题:只对单个JVM有效

  2. 分布式锁

    • 为啥需要分布式锁?
      1. 有限资源的情况下,控制同一时间(段)只有某些线程(用户/服务器)才能访问到资源。
      2. 单个锁只对单个JVM有效
  3. 分布式锁实现的关键

    • 抢锁机制

      • 怎么保证同一时间只有1个服务器抢到锁?
      • 核心思想:先来的人先把数据改成自己的标识(服务器ip),后来的人发现标识已经存在,就抢锁失败,继续等待。等先来的人执行方法结束,把标识清空,其他人继续抢锁。
      • MySQL数据库:select for update 行级锁(最简单)(乐观锁)
      • Redis实现:内存数据库,读写速度快。支持setnx、lua脚本,比较方便我们实现分布式锁。
        • setnx:set if not exists 如果不存在,则设置;只有设置成功才会返回true,否则返回false。
    • 注意事项

      1. 用完锁要释放(腾地方)
      2. 锁一定要加过期时间
      3. 如果方法执行时间过长,锁提前过期时
        • 会出现的问题:
          1. 连锁效应:释放别人的锁
          2. 还会存在多个方法同时执行的情况。
        • 解决方法:
          • 续期
      4. 释放锁的时候,有可能先判断出是自己的锁,然后锁过期了,最后还是释放了别人的锁。
        • 解决办法:使用redis的原子操作,在判断执行时不允许再设置锁。用redis+lua脚本实现。

第八部分#

  1. Redisson实现分布式锁

    • Redisson是一个java操作redis的客户端,提供了大量的分布式数据集来简化对redis的操作和使用,可以让开发者像使用本地集合一样使用redis,完全感知不到redis的存在。

    • 两种引入方式

      1. spring boot starter引入(不推荐)
      2. 直接引入
    • 首先对Redisson进行配置

      • image-20250120180527560
    • 再进行测试

      • image-20250120180611446
    • 测试通过后,编写对应的使用代码

      • image-20250120180718017
  2. 定时任务+锁

    • waitTime设置为0,只抢一次,抢不到就放弃。
    • 注意释放锁要写在finally中。
  3. 看门狗机制

    redisson中提供的续期机制

    • 开一个监听线程,如果方法还没执行完,就帮你重置redis锁的过期时间。
    • 原理:
      1. 监听当前线程,默认续期时间是30s,每10秒续期一次(补充道30s)
      2. 如果线程挂掉(注意debug模式也会被当成服务器宕机),则不会续期

第九部分#

  1. 需求分析

    • image-20250121150254785
  2. 数据库表设计

    create table team
    (
        id           bigint auto_increment comment 'id' primary key,
        name         varchar(256)                   not null  comment '队伍名称',
        description  varchar(1024)                      null comment '描述',
        maxNum       int             default 1      not null comment '最大人数',
        expireTime   datetime                           null comment '过期时间',
        userId       bigint                                  comment '用户Id',
        status       int             default 0      not null comment '状态,0-公开,1-私有,2-加密',
        password     varchar(512)                       null comment '密码',
        createTime   datetime default CURRENT_TIMESTAMP not null comment '创建时间',
        updateTime   datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
        isDelete     tinyint  default 0                 not null comment '是否删除'
    )comment '队伍';
    create table user_team
    (
        id           bigint auto_increment comment 'id' primary key,
        userId       bigint                                  comment '用户Id',
        teamId       bigint                                  comment '队伍Id',
        joinTime     datetime                           null comment '加入时间',
        createTime   datetime default CURRENT_TIMESTAMP not null comment '创建时间',
        updateTime   datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
        isDelete     tinyint  default 0                 not null comment '是否删除'
    )comment '用户队伍关系';
    
  3. 实现增删改查,细化判断条件

    • 例图:

      • image-20250121170115311

      • image-20250121170137626

      • image-20250121185148288

梦潭伙伴匹配项目总结
https://lym0518.cn/posts/partnermatching/
作者
TeFantasy
发布于
2023-08-17
许可协议
CC BY-NC-SA 4.0