首页 > 其他分享 >心链6----开发主页以及后端数据插入(多线程并发)定时任务

心链6----开发主页以及后端数据插入(多线程并发)定时任务

时间:2024-06-02 13:28:35浏览次数:23  
标签:---- userList 心链 new stopWatch import 多线程 com user

心链 — 伙伴匹配系统

开发主页

信息搜索页修改

image.png
image.png

主页开发(直接list用户)

在后端controller层编写接口去实现显示推荐页面的功能

    /**
     * 推荐页面
     * @param request
     * @return
     */
    @GetMapping("/recommend")
    public BaseResponse<List<User>> recommendUsers(HttpServletRequest request){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList = userService.list(queryWrapper);
        List<User> list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
        return ResultUtils.success(list);
    }

前端就先复制搜索结果的代码,在修改一个一些不需要的即可

<!--
User:Shier
CreateTime:14:47
-->
<template>
  <van-card
      v-for="user in userList"
      :desc="user.profile"
      :title="`${user.username} (${user.planetCode})`"
      :thumb="user.avatarUrl"
  >
    <template #tags>
      <van-tag plain type="danger" v-for="tag in tags" style="margin-right: 8px; margin-top: 8px">
        {{ tag }}
      </van-tag>
    </template>
    <template #footer>
      <van-button size="mini">联系我</van-button>
    </template>
  </van-card>
  <van-empty v-if="!userList || userList.length < 1" image="search" description="数据为空"/>
</template>

<script setup>
  import {onMounted, ref} from "vue";
  import {useRoute} from "vue-router";
  import {showFailToast, showSuccessToast} from "vant/lib/vant.es";
  import myAxios from "../plugins/myAxios.ts";

  import qs from 'qs'

  const route = useRoute();
  const {tags} = route.query;

  const userList = ref([]); //用户列表

  onMounted(async () => {
    // 为给定 ID 的 user 创建请求
    const userListData = await myAxios.get('/user/recommend', {
      withCredentials: false,
      params: {},
    })
        .then(function (response) {
          console.log('/user/recommend succeed', response);
          showSuccessToast('请求成功');
          return response?.data;
        })
        .catch(function (error) {
          console.log('/user/recommend error', error);
          showFailToast('请求失败')
        });
    if (userListData) {
      userListData.forEach(user => {
        if (user.tags) {
          user.tags = JSON.parse(user.tags);
        }
      })
      userList.value = userListData;
    }
  })

</script>

<style scoped>

</style>

image.png
修改一下页面边距
image.png

提取用户信息信息卡片

新建文件夹components和文件UserCardList.vue,将主页用户信息卡片提取出来。主页和用户信息搜索页进行引用。

<template>
  <van-card
      v-for="user in userList"
      :desc="user.profile"
      :title="`${user.username} (${user.planetCode})`"
      :thumb="user.avatarUrl"
  >
    <template #tags>
      <van-tag plain type="danger" v-for="tag in user.tags" style="margin-right: 8px; margin-top: 8px" >
        {{ tag }}
      </van-tag>
    </template>
    <template #footer>
      <van-button size="mini">联系我</van-button>
    </template>
  </van-card>
</template>

<script setup lang="ts">
import {UserType} from "../models/user";

interface UserCardListProps{
  userList: UserType[];
}
// 给父组件设置默认值,保证数据不为空
const props= withDefaults(defineProps<UserCardListProps>(),{
  //@ts-ignore
  userList: [] as UserType[]
});

</script>
<style scoped>
  /* 标签颜色*/
  .van-tag--danger.van-tag--plain {
    color: #002fff;
  }
</style>

然后在Index、SearchResultPage引入UserCardList
image.png

导入数据

模拟 1000 万个用户,再去查询

  1. 用可视化界面:适合一次性导入、数据量可控
  2. 写程序:for 循环,建议分批,不要一把梭哈(可以用接口来控制)要保证可

控、幂等,注意线上环境和测试环境是有区别的导入 1000 万条,for i 1000w

  1. 执行 SQL 语句:适用于小数据量

导入导出

(鱼皮这里应该是屏幕没有放大没有看见字段对应的列信息,idea是可以实现的。)
**导出 **
这里自己选择导出的文件类型和导出的地方路径。(尽量用CSV,exsl格式的话因为编码因为会乱码。)


导入
选择要导入的文件




(导入有风险,自己要想清楚用何种方式导入数据。鱼皮在视频里也重点说过的。)

定时任务

:::info
开启定时任务;注解。

新建InsertUser.java(鱼皮是在once文件夹,我这个是之前命名是起的,都可以无所谓的,自己记得就好。)

插件(idea里搜的)

编写定时任务代码并进行测试(这里的定时取巧,尽量别用,注释掉。)

:::

package com.yupi.usercenter.easyExcel;
import java.util.Date;

import com.yupi.usercenter.mapper.UserMapper;
import com.yupi.usercenter.model.domain.User;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;

@Component
public class InsertUsers {

    @Resource
    private UserMapper userMapper;

    /**
     * 循环插入用户
     */
//    @Scheduled(initialDelay = 5000,fixedRate = Long.MAX_VALUE )
    public void doInsertUser() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 1000;
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUsername("假用户");
            user.setUserAccount("sifsf");
            user.setAvatarUrl("https://raw.githubusercontent.com/RockIvy/images/master/img/avatar54.jpg");
            user.setProfile("阿尼亚");
            user.setGender(0);
            user.setUserPassword("12345678");
            user.setPhone("123456789108");
            user.setEmail("[email protected]");
            user.setUserStatus(0);
            user.setUserRole(0);
            user.setPlanetCode("931");
            user.setTags("[]");
            userMapper.insert(user);
        }
        stopWatch.stop();
        System.out.println( stopWatch.getLastTaskTimeMillis());

    }
}

数据插入/并发插入

我们需要插入数据: 1.用可视化界面:适合一次性导入、数据量可控 由于编码,主键以及某些字段的问题
(id,createtime等),演示插入失败,这里不推荐 2.写程序:for 循环,建议分批,不要一把梭哈,这里
演示了两种插入数据的方法 首先创建测试方法文件InsertUsersTest,编写批量查询解决

并发执行,这里的线程可自定义或者用idea默认的,两种方法的区别是,自定义可以跑满线程,而默认的
只能跑CPU核数-1,代码区别:就是在异步执行处加上自定义的线程名

并发插入(这里数据量是100000)
并发要注意执行的先后顺序无所谓,不要用到非并发类的集合

private ExecutorService executorService = new ThreadPoolExecutor(16, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));

// CPU 密集型:分配的核心线程数 = CPU - 1
// IO 密集型:分配的核心线程数可以大于 CPU 核数

:::info
用户插入单元测试,注意打包时要删掉或忽略,不然打一次包就插入一次
:::

package com.ivy.usercenter.service;

import com.ivy.usercenter.mapper.UserMapper;
import com.ivy.usercenter.model.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author ivy
 * @date 2024/5/30 17:00
 */
@SpringBootTest
public class InsertUsersTest {

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserService userService;

    //线程设置
    private ExecutorService executorService = new ThreadPoolExecutor(16, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000));

    /**
     * 循环插入用户 10000 条耗时20000ms
     */
    @Test
    public void doInsertUser1() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 10000;
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUsername("假用户");
            user.setUserAccount("sifsf");
            user.setAvatarUrl("https://raw.githubusercontent.com/RockIvy/images/master/img/avatar54.jpg");
            user.setProfile("阿尼亚");
            user.setGender(0);
            user.setUserPassword("12345678");
            user.setPhone("123456789108");
            user.setEmail("[email protected]");
            user.setUserStatus(0);
            user.setUserRole(0);
            user.setPlanetCode("931");
            user.setTags("[]");
            userMapper.insert(user);
        }
        stopWatch.stop();
        System.out.println(stopWatch.getLastTaskTimeMillis());

    }

    /**
     * 循环插入用户  耗时:20000ms
     * 批量插入用户   10000  耗时: 1817ms
     */
    @Test
    public void doInsertUser2() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 10000;
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < INSERT_NUM; i++) {
            User user = new User();
            user.setUsername("假数据");
            user.setUserAccount("fakeaccount");
            user.setAvatarUrl("https://img0.baidu.com/it/u=3514514443,3153875602&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500");
            user.setGender(0);
            user.setUserPassword("231313123");
            user.setPhone("1231312");
            user.setEmail("[email protected]");
            user.setUserStatus(0);
            user.setUserRole(0);
            user.setPlanetCode("213123");
            user.setTags("[]");
            userList.add(user);
        }
        userService.saveBatch(userList, 1000);
        stopWatch.stop();
        System.out.println(stopWatch.getLastTaskTimeMillis());

    }

    /**
     * 并发批量插入用户   100000  耗时: 4769ms
     */
    @Test
    public void doConcurrencyInsertUser() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        final int INSERT_NUM = 100000;
        // 分十组
        int j = 0;
        //批量插入数据的大小
        int batchSize = 5000;
        List<CompletableFuture<Void>> futureList = new ArrayList<>();
        // i 要根据数据量和插入批量来计算需要循环的次数。(鱼皮这里直接取了个值,会有问题,我这里随便写的)
        for (int i = 0; i < INSERT_NUM / batchSize; i++) {
            List<User> userList = new ArrayList<>();
            while (true) {
                j++;
                User user = new User();
                user.setUsername("假shier");
                user.setUserAccount("shier");
                user.setAvatarUrl("https://c-ssl.dtstatic.com/uploads/blog/202101/11/20210111220519_7da89.thumb.1000_0.jpeg");
                user.setProfile("fat cat");
                user.setGender(1);
                user.setUserPassword("12345678");
                user.setPhone("123456789108");
                user.setEmail("[email protected]");
                user.setUserStatus(0);
                user.setUserRole(0);
                user.setPlanetCode("33322");
                user.setTags("[]");
                userList.add(user);
                if (j % batchSize == 0) {
                    break;
                }
            }
            //异步执行 使用CompletableFuture开启异步任务
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                System.out.println("ThreadName:" + Thread.currentThread().getName());
                userService.saveBatch(userList, batchSize);
            }, executorService);
            futureList.add(future);
        }
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join();

        stopWatch.stop();
        System.out.println(stopWatch.getLastTaskTimeMillis());
    }

}

若使用默认线程池,删去
image.png

分页查询

现在启动前后端,查看主页,发现搜查不出,这是因为数据太多需要分页,修改后端接口方法

    /**
     * 推荐页面
     * @param request
     * @return
     */
    @GetMapping("/recommend")
    public BaseResponse<Page<User>> recommendUsers(long pageSize,long pageNum, HttpServletRequest request){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        Page<User> userList = userService.page(new Page<>(pageNum, pageSize), queryWrapper);
        return ResultUtils.success(userList);
    }

同时还要引入mybatis的分页插件配置,直接复制文档到config目录

主要不要忘了把扫包的路径改为自己的

package com.yupi.usercenter.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.yupi.usercenter.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}

现在去修改前端主页
image.png
image.png

标签:----,userList,心链,new,stopWatch,import,多线程,com,user
From: https://blog.csdn.net/PQ781826/article/details/139331051

相关文章

  • 【GD32F303红枫派使用手册】第六节 PMU-低功耗实验
    6.1实验内容通过本实验主要学习以下内容:PMU原理;低功耗的进入以及退出操作;6.2实验原理6.2.1PMU结构原理PMU即电源管理单元,其内部结构下图所示,由该图可知,GD32F303系列MCU具有三个电源域,包括VDD/VDDA电源域、1.2V电源域以及电池备份域,其中,VDD/VDDA域由电源直接供电。在......
  • 阿里云开发者社区有奖征文活动,期待您出文相助
    和阿里云开发者社区的合作曾经是园子的收入来源之一,但现在合作机会越来越少了,今年好不容易等到一次合作机会,就是这次的有奖征文活动——「寻找热爱技术创作的你:写下你在技术探中的实践和思考」,详见活动公告这次征文合作分2期,第1期需要完成保底提交50篇符合要求的文章才能拿到收......
  • MySQL 权限详解
    All/AllPrivileges权限代表全局或者全数据库对象级别的所有权限Alter权限代表允许修改表结构的权限,但必须要求有create和insert权限配合。如果是rename表名,则要求有alter和drop原表,create和insert新表的权限Alterroutine权限代表允许修改或者删除存储过程、函数的权限Create......
  • [leetcode 第 400 场周赛]题解
    第一题:classSolution{publicintminimumChairs(Strings){intx=0;intans=0;for(inti=0;i<s.length();i++){if(s.charAt(i)=='E'){x--;if(x<0){ans++;x=0;......
  • 『手撕Vue-CLI』下载指定模板
    开篇经上篇文章的介绍,实现了获取下载目录地址,接下来实现下载指定模板的功能。背景通过很多章节过后,已经可以拿到模板名称,模板版本号,下载目录地址,这些信息都是为了下载指定模板做准备的。实现如何从GitHub下载模板可以借助download-git-repo这个库来下载GitHub上的模......
  • 班级网页制作 HTML个人网页设计 我的班级网站设计与实现 大学生简单班级静态HTML网页
    ......
  • 个人介绍网页代码 html静态网页设计制作 dw静态网页成品模板素材网页 web前端网页设计
    ......
  • 通过指针变量访问整型变量
    有两个与指针变量有关的运算符:(1)&:取地址运算符。(2)*:指针运算符(或称间接访问运算符)。例如:&a为变量a的地址,*p为指针变量p所指向的存储单元。编写程序:运行结果:程序分析:        (1)在程序第5行虽然定义了两个指针变量pointer_1和pointer_2,但它们并未指向任何一个......
  • 6.2 休息日 背包问题总结
    就目前所遇到的01背包与完全背包作总结。01背包有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。二维dp数组01背包动规五部曲1.确定dp数组以及下标的含义dp[i......
  • day45 1049.最后一块石头的重量II 494.目标和 474.一和零
    1049.最后一块石头的重量II本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。本题物品的重量为stones[i],物品的价值也为stones[i]。对应着01背包里的物品重量weight[i]和物品价值value[i]。思路:动规五部曲1.确定dp数组以及下......