springboot_vue知识点

代码放到了仓库。

springboot_vue知识点

  • 1.搭建
    • 1.vue
    • 2.springboot
  • 2.前后端请求和响应的封装
    • 1.请求封装
    • 2.响应封装
  • 3.增删改查
    • 1.查询
    • 2.分页
    • 3.新增和编辑
    • 4.删除
  • 4.跨域和自定义异常
  • 5.JWT鉴权
    • 1.配置pom
    • 2.拦截前端请求的拦截器
    • 3.生成token并验证token
    • 4.登录后生成token
    • 5.前端获取token然后每次请求时header带着token
    • 6.后端jwt拦截器
    • 7.使用jwt拦截器拦截前端请求
  • 6.文件的上传下载
    • 1.上传
    • 2.下载
  • 7.批量删除
  • 8.数据库导入导出excel文件
    • 1.导出
    • 2.导入
  • 9.模块关联
    • 1.service映射
    • 2.mapper关联
  • 10.角色管理
  • 11.审批功能
  • 12.预约功能
  • 13.AOP日志管理
    • 1.依赖
    • 2.自定义注解
    • 3.AOP切面处理
    • 4.在controller的方法里面使用自定义的注解
  • 14.图形验证码
    • 1.依赖
    • 2.定义Mapper映射格式
    • 3.生成验证码的控制器
    • 4.登陆页面的key和验证码请求
    • 5.后端登录的验证
  • 15.Echarts
    • 1.饼状图
    • 2.折线图和柱状图
  • 16.富文本
  • 效果

1.搭建

1.vue

npm install -g @vue@cli
vue create yourproject#手动选择babel和router,3
npm run serve
npm install element-plus#安装
#main.js里面全局使用
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus, { size: "small" })
app.mount('#app')
#main.js导入样式,清除控件自带
import '@/assets/global.css'
body {margin: 0;padding: 0;overflow: hidden;
}
/*把所有的元素变成盒状模型*/
* {/*外边距不会额外占用1px的像素*/box-sizing: border-box;
}

然后在App.vue里面使用el-container配置页面布局

<el-container><el-header style="background-color: #4c535a"></el-header>
</el-container><el-container><el-aside style="overflow: hidden; min-height: 100vh; background-color: #545c64; width: 250px"></el-aside><el-main></el-main>
</el-container>

左侧的menu绑定路由

#1.首先在 el-menu 标签里绑定 default-active 为路由的形式::default-active="$route.path" router
<el-menu :default-active="$route.path" router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
#2.然后将 <el-menu-item> 标签里的index属性值设置成对应的路由
<el-menu-item index="/admin">管理员信息</el-menu-item>
#3.在 router/index.js 里添加对应路由配置
{path: '/admin',name: 'AdminView',component: AdminView},
#4.去掉menu小滚轮
<style>
.el-menu{border-right: none !important;
}
</style>

el-table用:data="tableData",el-table-column用prop="name"绑定表单数据。

2.springboot

创建数据库和表,然后创建spring工程,依赖选择web就可以,然后在pom里面添加依赖:
这里遇到Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required错误,好像是因为springboot3不支持mybatis-spring-boot-starter 2.x 及以下版本,所以就去https://mvnrepository.com/搜索最新的MyBatis Spring Boot Starter ,这里我用了3.0.2。

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>4.1.5</version></dependency></dependencies><repositories><!-- 由于未正式发版,所以在Maven仓库里还搜不到,需要额外配置一个远程仓库 --><repository><id>ossrh</id><name>OSS Snapshot repository</name><url>https://oss.sonatype.org/content/repositories/snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories>

在application.yml中添加配置

server:port: 8181
# 数据库配置
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: root   #你本地的数据库用户名password: xxx #你本地的数据库密码url: jdbc:mysql://localhost:3306/knowledges?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=true
# 配置mybatis实体和xml映射
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.hckj.springboot.entity

跨域问题可以在controller上加个注解:@CrossOrigin

2.前后端请求和响应的封装

1.请求封装

前端请求用到了axios,所以先安装npm i axios -S,然后在src/utils/request.js里面封装前端请求的格式:
请求基地址,响应时间,请求头,拿到后端返回的result(response.data),以后就可以用import request from '@/utils/request'使用request去请求了。

import axois from 'axios';
//1.创建一个axios对象
const request=axois.create({baseURL:'http://localhost:8181',timeout:5000});
//2.request拦截器:请求发送前对请求做一些处理,比如统一加token,对请求参数统一加密
request.interceptors.request.use(config=>{config.headers['Content-Type']='application/json;charset=utf-8';//config.headers['token']=user.token;//设置请求头return config
},error=>{return Promise.reject(error)})
//3.response拦截器:接口响应后统一处理结果
request.interceptors.response.use(response=>{let res=response.data;if (typeof res==='string'){res=res?JSON.parse(res):res}return res;
},error => {console.log('err'+error)return Promise.reject(error)})
//4.导出配置好的request
export default request

2.响应封装

在common/Result.java里面封装响应,包括code,msg,data并定义常用的success和error响应:

package com.hckj.springboot.common;public class Result {private static final String SUCCESS="0";private static final String ERROR="-1";private String code;private String msg;private Object data;public static Result success(){Result result=new Result();result.setCode(SUCCESS);return result;}public static Result success(Object data){Result result=new Result();result.setCode(SUCCESS);result.setData(data);return result;}public static Result error(String msg){Result result=new Result();result.setCode(ERROR);result.setMsg(msg);return  result;}//get和set方法

这样,后端在给前端数据时都是Result类型,并调用里面的success和error方法。

3.增删改查

1.查询

将全部查询和按条件查询写到一个接口里,因为进来要全部查询,所以函数要挂载到onMounted上;条件查询,所以要给查询按钮绑定点击事件;然后在xml里面通过sql语句按条件查询和全部查询。
后端

#1.参数和数据库表都创建实体类
public class Params {private String name;private  String phone;//get,set方法
}
@Table(name="admin")//这里必须是双引号
public class Admin {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@Column(name = "name")private String name;@Column(name = "password")private String password;@Column(name = "sex")private String sex;@Column(name = "age")private Integer age;@Column(name = "phone")private String phone;//get,set方法
}
#2.dao接口和xml
@Repository
public interface AdminDao extends Mapper<Admin> {List<Admin> findBySearch(@Param("params") Params params);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hckj.springboot.dao.AdminDao"><select id="findBySearch" resultType="com.hckj.springboot.entity.Admin">select * from admin<where><if test="params != null and params.name != null and params.name != ''">and name like concat('%', #{ params.name }, '%')</if><if test="params != null and params.phone != null and params.phone != ''">and phone like concat('%', #{ params.phone }, '%')</if></where></select>
</mapper>
# 3.service类
@Service
public class AdminService {@Autowiredprivate AdminDao adminDao;public List<Admin> findBySearch(Params params) {return adminDao.findBySearch(params);}}
#4.controller,通过封装的result返回前端数据
@GetMapping("/search")public Result findBySearch(Params params){List<Admin> list = adminService.findBySearch(params);return Result.success(list);

前端,这里初始化ref变量的时候注意是列表[]还是对象{},赋值和取值的时候记得加.value,然后变量和方法都要return。

<template><div class="about"><div><el-input v-model="searchparams.name" style="width: 200px" placeholder="请输入姓名"></el-input><el-input v-model="searchparams.phone" style="width: 200px; margin-left: 5px" placeholder="请输入电话"></el-input><el-button type="warning" style="margin-left: 10px" @click="findBySearch()">查询</el-button><el-button type="primary" style="margin-left: 10px" >新增</el-button></div><div><el-table :data="tableData" style="width: 100%; margin: 15px 0px"><el-table-column prop="name" label="姓名" width="180"></el-table-column><el-table-column prop="sex" label="姓别" width="180"></el-table-column><el-table-column prop="age" label="年龄"></el-table-column><el-table-column prop="phone" label="电话"></el-table-column><el-table-column label="操作"><el-button type="primary">编辑</el-button><el-button type="danger">删除</el-button></el-table-column></el-table></div></div>
</template>
<script>
import {ref,onMounted} from "vue";
import request from '@/utils/request'
export default {setup(){const tableData=ref([]);const searchparams=ref({name:"",phone:"",});const findBySearch=()=>{request.get("/search",{params:searchparams.value}).then((res)=>{if(res.code==="0"){tableData.value=res.data;}})};onMounted(()=>{findBySearch();});return{tableData,searchparams,findBySearch,}}
}
</script>

2.分页

后端
1.首先pom添加依赖:

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.7</version>
</dependency>

2.application.yml里面写分页配置

#配置分页
pagehelper:helper-dialect: mysqlreasonable: truesupport-methods-arguments: trueparams: count=countSql

3.修改service和controler层

//1.service层里面首先开启分页查询,然后返回时将数据类型变为PageInfopublic PageInfo<Admin>  findBySearch(Params params) {// 开启分页查询PageHelper.startPage(params.getPageNum(), params.getPageSize());// 接下来的查询会自动按照当前开启的分页设置来查询List<Admin> list = adminDao.findBySearch(params);return PageInfo.of(list);}
//2.controller层里面调用service层时返回的数据类型改为PageInfo即可
PageInfo<Admin> list = adminService.findBySearch(params);

4.前端
在vue组件里面添加el-pagination组件,然后在script里面配置参数:

<el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="searchparams.pageNum":page-sizes="[5, 10, 15, 20]":page-size="searchparams.pageSize"layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination>
//这里面涉及到方法2个,参数3个;其中两个参数放到searchparams里面传给后端,total不用传给后端,后端会返回过来数据,然后赋值给total,最后将参数和方法return
const searchparams=ref({name:"",phone:"",pageNum: 1,pageSize: 5});const total =ref( 0);const findBySearch=()=>{request.get("/search",{params:searchparams.value}).then((res)=>{if(res.code==="0"){tableData.value=res.data.list;total.value = res.data.total;}})};function  handleSizeChange(pageSize){searchparams.value.pageSize=pageSize;findBySearch();}function  handleCurrentChange(pageNum){searchparams.value.pageNum=pageNum;findBySearch();}

3.新增和编辑

1.首先给新增和编辑添加click事件,然后使用el-dialog填写表单信息,编辑的时候使用v-slot绑定就可以拿到这条数据信息,这两个前端的区分就是form数据。

<el-button type="primary" style="margin-left: 10px" @click="add()">新增</el-button>
<el-table-column label="操作" v-slot="scope"><el-button type="primary" @click="edit(scope.row)">编辑</el-button><el-button type="danger">删除</el-button>
</el-table-column>
const form=ref({});
const add=()=>{form.value={};dialogFormVisible.value=true;};
const edit=(obj)=>{form.value=obj;dialogFormVisible.value=true;
}

2.然后就是form表单,这里使用了el-dialog和el-form,取消的话就关闭,确定的话就向后端发送数据进行请求。

<el-dialog title="用户信息" v-model="dialogFormVisible" ><el-form :model="form"><el-form-item label="姓名" label-width="15%"><el-input v-model="form.name" autocomplete="off" style="width:90%"></el-input></el-form-item><el-form-item label="性别" label-width="15%"><el-radio v-model="form.sex" label="男"></el-radio><el-radio v-model="form.sex" label="女"></el-radio></el-form-item><el-form-item label="年龄" label-width="15%"><el-input v-model="form.age" autocomplete="off" style="width: 90%"></el-input></el-form-item><el-form-item label="电话" label-width="15%"><el-input v-model="form.phone" autocomplete="off" style="width: 90%"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogFormVisible = false">取 消</el-button><el-button type="primary" @click="submit()">确 定</el-button></div></el-dialog>const submit=()=>{request.post('addedit',form.value).then((res)=>{if (res.code==="0"){dialogFormVisible.value=false;findBySearch();}})}

3.后端拿到数据根据id判断时新增还是编辑,然后通过controller和service层完成操作。

@PostMapping("/addedit")public Result save(@RequestBody Admin admin){if (admin.getId()==null){//新增adminService.add(admin);}else{//编辑adminService.update(admin);}return Result.success();}public void add(Admin admin){if (admin.getPassword() == null) {admin.setPassword("123456");}adminDao.insertSelective(admin);//通过掉包实现插入数据,不用再去操作dao层}public void update(Admin admin) {adminDao.updateByPrimaryKeySelective(admin);//同上}

5.error:java.lang.NoSuchMethodException: tk.mybatis.mapper.provider.SpecialProvider.()
解决方法:mapperscan包从tk中导入 import tk.mybatis.spring.annotation.MapperScan;

4.删除

1.删除按钮使用popconfirm进行二次确认:

<el-popconfirm title="确定删除吗?" @confirm="del(scope.row.id)"><template #reference><el-button slot="reference" type="danger" style="margin-left: 5px">删除</el-button></template>></el-popconfirm>

2.当confirm确认时,就向后端发送删除请求:

const del=(id)=> {request.delete("/del/" + id).then((res)=> {if (res.code === '0') {findBySearch();}})}

3.后端处理

@DeleteMapping("/del/{id}")public Result delete(@PathVariable Integer id){adminService.delete(id);return Result.success();}
public void delete(Integer id) {adminDao.deleteByPrimaryKey(id);}

4.跨域和自定义异常

1.跨域问题,后端common里面加一个CorsConfig.java

package com.hckj.springboot.common;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class CorsConfig {@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置return new CorsFilter(source);}
}

2.自定义异常捕获,在exception里面先建GlobalException:

@ControllerAdvice(basePackages="com.hckj.springboot.controller")
public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);//统一异常处理@ExceptionHandler,主要用于Exception@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(HttpServletRequest request, Exception e){log.error("异常信息:",e);return Result.error("系统异常");}@ExceptionHandler(CustomException.class)@ResponseBodypublic Result customError(HttpServletRequest request, CustomException e){return Result.error(e.getMsg());}
}

然后相同目录下新建CustomException自定义异常msg:

public class CustomException extends RuntimeException {private String msg;public CustomException(String msg) {this.msg = msg;}get和set方法
}

5.JWT鉴权

1.首先用户登录之后将后台返回的用户信息保存到浏览器的localstorage中:

localStorage.setItem("user", JSON.stringify(res.data));

2.在页面右上角拿到localstorage的user数据,显示username,退出登陆时删除localstorage里面的user信息:

localStorage.setItem("user", JSON.stringify(res.data));
<el-dropdown style="float: right; height: 60px; line-height: 60px"><span class="el-dropdown-link" style="color: white; font-size: 16px">{{ user.name }}<el-icon class="el-icon--right"><arrow-down /></el-icon></span><template #dropdown><el-dropdown-item><div @click="logout">退出登录</div></el-dropdown-item></template>
</el-dropdown>
const logout=()=>{localStorage.removeItem("user");router.push("/login")};

3.任何人都可以通过路由访问主页等信息,不安全,所以在前端做一个路由守卫,如果localstorage里面没有user的信息就只能去注册和登录页面:

router.beforeEach((to ,from, next) => {if (to.path ==='/login'|| to.path==='/register') {next();}const user = localStorage.getItem("user");if (!user && to.path !== '/login' && to.path !== '/register'){return next("/login");}next();
})

4.这样就只有localstorage里面有user:“xxx”数据才可以,但是这个数据可以伪造,所以就用到了jwt:在用户登录后,后台给前台发送一个凭证(token),前台请求的时候需要带上这个凭证(token),才可以访问接口,如果没有凭证或者凭证跟后台创建的不一致,则说明该用户不合法。

1.配置pom

添加依赖

<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version>
</dependency>

2.拦截前端请求的拦截器

给后台接口加上统一的前缀/api,然后我们统一拦截该前缀开头的接口,所以在common/WebConfig.java配置一个拦截器。

@Configuration
public class WebConfig implements  WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {// 指定controller统一的接口前缀configurer.addPathPrefix("/api", clazz -> clazz.isAnnotationPresent(RestController.class));}
}

记得给前端请求的拦截器request封装里面,baseUrl也加个 /api 前缀。

3.生成token并验证token

在common/JwtTokenUtils.java里面genToken利用用户的id和密码生成一个有效期2小时的Token,getCurrentUser根据token解码到id,然后查找用户是否存在:

@Component
public class JwtTokenUtils {private static AdminService staticAdminService;private static final Logger log = LoggerFactory.getLogger(JwtTokenUtils.class);@Resourceprivate AdminService adminService;@PostConstructpublic void setUserService() {staticAdminService = adminService;}/*** 生成token*/public static String genToken(String adminId, String sign) {return JWT.create().withAudience(adminId) // 将 user id 保存到 token 里面,作为载荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期.sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥}/*** 获取当前登录的用户信息*/public static Admin getCurrentUser() {String token = null;try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();token = request.getHeader("token");if (StrUtil.isBlank(token)) {token = request.getParameter("token");}if (StrUtil.isBlank(token)) {log.error("获取当前登录的token失败, token: {}", token);return null;}// 解析token,获取用户的idString adminId = JWT.decode(token).getAudience().get(0);return staticAdminService.findById(Integer.valueOf(adminId));} catch (Exception e) {log.error("获取当前登录的管理员信息失败, token={}", token,  e);return null;}}
}

在service里面添加一个利用id找用户

public Admin findById(Integer id) {return adminDao.selectByPrimaryKey(id);}

4.登录后生成token

在登录的service层里面,当用户登陆成功后,利用上面的函数生成token:

String token = JwtTokenUtils.genToken(user.getId().toString(), user.getPassword());
user.setToken(token);//这里给admin实体添加一个token

这里给用户实体类添加一个暂时的token属性,然后setget方法:

@Transient//不需要被持久化或序列化的临时数据或敏感数据
private String token;

5.前端获取token然后每次请求时header带着token

因为登录后返回的用户信息保存在了localstorage里面,所以在request.js封装的request请求里面从localstorage里面拿到token,然后放到请求头里面:

const user = localStorage.getItem("user");
if (user) {config.headers['token'] = JSON.parse(user).token;
}

这样的话如果登录了并拿到了token,2小时之内向后端请求的话header会带有token去给后端验证。

6.后端jwt拦截器

在common/JwtInterceptor.java里面拦截http请求,验证token:

@Component
public class JwtInterceptor implements HandlerInterceptor {private static final Logger log = LoggerFactory.getLogger(JwtInterceptor.class);@Resourceprivate AdminService adminService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从http请求的header中获取tokenString token = request.getHeader("token");if (StrUtil.isBlank(token)) {// 如果没拿到,我再去参数里面拿一波试试  /api/admin?token=xxxxxtoken = request.getParameter("token");}// 2. 开始执行认证if (StrUtil.isBlank(token)) {throw new CustomException("无token,请重新登录");}// 获取 token 中的userIdString userId;Admin admin;try {userId = JWT.decode(token).getAudience().get(0);// 根据token中的userid查询数据库admin = adminService.findById(Integer.parseInt(userId));} catch (Exception e) {String errMsg = "token验证失败,请重新登录";log.error(errMsg + ", token=" + token, e);throw new CustomException(errMsg);}if (admin == null) {throw new CustomException("用户不存在,请重新登录");}try {// 用户密码加签验证 tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(admin.getPassword())).build();jwtVerifier.verify(token); // 验证token} catch (JWTVerificationException e) {throw new CustomException("token验证失败,请重新登录");}return true;}
}

7.使用jwt拦截器拦截前端请求

将上面的拦截功能在common/webConfig里面使用拦截,过滤掉登录注册等白名单路由:

@Resource
private JwtInterceptor jwtInterceptor;// 加自定义拦截器JwtInterceptor,设置拦截规则
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/login").excludePathPatterns("/api/register");
}

6.文件的上传下载

1.上传

1.后端FileController.java里面写文件上传的控制器,这里用到了hutool这个依赖去将上传的文件写入到服务器的指定位置,然后将文件名里面的时间戳返回到前端,前端拿到时间戳再和表单里的其他信息一起保存,时间戳保存到img字段。

private static final String filePath = System.getProperty("user.dir") + "/file/";
@PostMapping("/upload")
public Result upload(MultipartFile file) {synchronized (FileController.class) {String flag = System.currentTimeMillis() + "";String fileName = file.getOriginalFilename();try {if (!FileUtil.isDirectory(filePath)) {FileUtil.mkdir(filePath);}// 文件存储形式:时间戳-文件名FileUtil.writeBytes(file.getBytes(), filePath + flag + "-" + fileName);System.out.println(fileName + "--上传成功");Thread.sleep(1L);} catch (Exception e) {System.err.println(fileName + "--文件上传失败");}return Result.success(flag);}
}

2.因为文件上传没有走http请求,所以没有header的token,这里有两种方式,一种是在后端的webconfig拦截器里面放行,另一种是给加上token,这里用第一种:.excludePathPatterns("/api/files/**")
3.前端写上传文件的el-upload和拿后端给的时间戳:

<el-form-item label="图书封面" label-width="15%"><el-upload action="http://localhost:8181/api/files/upload" :on-success="successUpload"><el-button  type="primary">点击上传</el-button></el-upload>
</el-form-item>
function successUpload(res){form.value.img=res.data;
}

2.下载

1.FileController.java里面写文件下载的get请求。

@GetMapping("/{flag}")public void avatarPath(@PathVariable String flag, HttpServletResponse response) {if (!FileUtil.isDirectory(filePath)) {FileUtil.mkdir(filePath);}OutputStream os;List<String> fileNames = FileUtil.listFileNames(filePath);String avatar = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse("");try {if (StrUtil.isNotEmpty(avatar)) {response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(avatar, "UTF-8"));response.setContentType("application/octet-stream");byte[] bytes = FileUtil.readBytes(filePath + avatar);os = response.getOutputStream();os.write(bytes);os.flush();os.close();}} catch (Exception e) {System.out.println("文件下载失败");}}

2.下载到前端页面进行显示

<el-table-column label="图书封面"><template v-slot="scope"><el-imagestyle="width: 70px; height: 70px; border-radius: 50%":src="'http://localhost:8181/api/files/' + scope.row.img":preview-src-list="['http://localhost:8181/api/files/' + scope.row.img]"></el-image></template>
</el-table-column>

3.点击下载按钮,通过浏览器下载到本地

 <el-button type="primary" @click="down(scope.row.img)">下载</el-button>const down=(flag)=>{window.location.href = `http://localhost:8181/api/files/${flag}`;};

7.批量删除

1.首先是在table里面在条数据前面加一个勾选框,然后每次点选都有触发事件:

<el-table :data="tableData" style="width: 100%" ref="table" @selection-change="handleSelectionChange" :row-key="getRowKeys"><el-table-column ref="table" type="selection" width="55" align="center" :reserve-selection="true"></el-table-column>
</el-table>
const multipleSelection = ref([]);
const handleSelectionChange = (val) => {multipleSelection.value = val;
};
const getRowKeys = (row) => {return row.id;
};

2.批量删除的二次确认按钮,并触发后端请求事件

<el-popconfirm title="确定删除这些数据吗?" @confirm="delBatch()"><template #reference><el-button slot="reference" type="danger" style="margin-left: 5px">批量删除</el-button></template>>
</el-popconfirm>
import { ElMessage } from 'element-plus';
const delBatch = () => {if (multipleSelection.value.length === 0) {ElMessage.warning("请勾选您要删除的项");return;}request.put("/type/delBatch", multipleSelection.value).then(res => {if (res.code === '0') {ElMessage.success("批量删除成功");findBySearch(); // 请确保你的 `findBySearch` 方法在这个作用域中是可用的} else {ElMessage.error(res.msg);}});};

3.后端在controller层里面利用for循环调用del:

@PutMapping("/delBatch")public Result delBatch(@RequestBody List<Type> list) {for (Type type : list) {typeService.delete(type.getId());}return Result.success();}

8.数据库导入导出excel文件

1.导出

1.首先前端有一个导出按钮,然后点击之后带着token像后端发送请求,因为不是走request,所以拼接上token(或者在后端放行)。

<el-button type="success" style="margin-left: 10px" @click="exp()">导出报表</el-button>
const exp=()=>{const user = JSON.parse(localStorage.getItem("user"));if (user) {const token = user.token;window.location.href = `http://localhost:8181/api/type/export?token=${token}`;}
};

2.后端

@GetMapping("/export")
public Result export(HttpServletResponse response) throws IOException {// 思考:// 要一行一行的组装数据,塞到一个list里面// 每一行数据,其实就对应数据库表中的一行数据,也就是对应Java的一个实体类Type// 我们怎么知道它某一列就是对应某个表头呢?? 需要映射数据,我们需要一个Map<key,value>,把这个map塞到list里// 1. 从数据库中查询出所有数据List<Type> all = typeService.findAll();if (CollectionUtil.isEmpty(all)) {throw new CustomException("未找到数据");}// 2. 定义一个 List,存储处理之后的数据,用于塞到 list 里List<Map<String, Object>> list = new ArrayList<>(all.size());// 3. 定义Map<key,value> 出来,遍历每一条数据,然后封装到 Map<key,value> 里,把这个 map 塞到 list 里for (Type type : all) {Map<String, Object> row = new HashMap<>();row.put("图书类别名称", type.getName());row.put("图书类别描述", type.getDescription());list.add(row);}// 4. 创建一个 ExcelWriter,把 list 数据用这个writer写出来(生成出来)ExcelWriter wr = ExcelUtil.getWriter(true);wr.write(list, true);// 5. 把这个 excel 下载下来response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");response.setHeader("Content-Disposition","attachment;filename=type.xlsx");ServletOutputStream out = response.getOutputStream();wr.flush(out, true);wr.close();IoUtil.close(System.out);return Result.success();}

2.导入

1.首先是前端的导入按钮,这里让它post访问后端的接口,因为没有带token并没有使用request封装,所以在后端拦截器里面给他放行。

<el-upload action="http://localhost:8181/api/type/upload" style="display: inline-block; margin-left: 10px" :show-file-list="false" :on-success="successUpload"><el-button size="small" type="primary">批量导入</el-button>
</el-upload>
const successUpload=(res)=>{if (res.code==='0'){ElMessage.success("批量导入成功");}else{ElMessage.error(res.msg);}
}
.excludePathPatterns("/api/type/upload")

2.后端在controller里面读取excel并将数据写入数据库

@PostMapping("/upload")public Result upload(MultipartFile file) throws IOException {List<Type> infoList = ExcelUtil.getReader(file.getInputStream()).readAll(Type.class);if (!CollectionUtil.isEmpty(infoList)) {for (Type type : infoList) {try {typeService.add(type);} catch (Exception e) {e.printStackTrace();}}}return Result.success();}

3.这里需要注意:excel里面的表头要和数据库里面的表头对应,所以在实体类里面添加@Alias("分类名称")注解,即列的别名或描述信息。

@Column(name = "name")
@Alias("图书类别名称")
private String name;
@Column(name = "description")
@Alias("图书类别描述")
private String description;

9.模块关联

这里用图书和图书类别为实例,需要给图书表里面添加字段typeId,用来关联类别表里面的id,然后记得给图书Book实体类添加这个字段映射,然后在图书列表里面也显示这一列。

@Column(name="typeId")
private Integer typeId;

然后前端遍历type表,将type信息放到下拉选框里,让用户选择,并显示在book信息列。

<el-table-column prop="typeId" label="图书分类"></el-table-column>//1.table添加这一列显示
<el-form-item label="图书分类" label-width="15%">//2.form表单的下拉选择,这里遍历了typeObjs列表,然后将用户选的id放到form.typeId。<el-select v-model="form.typeId" placeholder="请选择" style="width: 90%"><el-option v-for="item in typeObjs" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
</el-form-item>
//3.拿到typeid的功能要放在onmounted里面,最后这个列表和方法都要returnconst typeObjs=ref([]);const findTypes=()=>{request.get("/type").then((res)=>{if(res.code==='0'){typeObjs.value=res.data;}else{ElMessage.error(res.msg);}})
}
onMounted(()=>{findTypes();
});

后端就在type控制层里面拿到type表里的所有信息。

@GetMapping
public Result findAll() {return Result.success(typeService.findAll());
}

此时,book信息就会显示图书类别这一列,并在form里面有下拉框遍历了type让用户选,但是用户选择后拿到的typeid,这是int类型的数据,所以还需要根据这个id去type表里面拿到对应的name,显示到前端。这里有两种方法,一种是在service层,将拿到的图书列表信息的typeid在type表里面通过id 查到那么,返回给前端;另一种方式是在mapper层通过关联两张表拿到type.name。这里要注意,因为book表里面只有typeid这个字段,但是没有typename这个字段,所以需要在实体类里面添加@Transient注解,然后在前端table显示时prop字段用typename

@Transient
private String typeName;
<el-table-column prop="typeName" label="图书分类"></el-table-column>

这里两种方式都演示以下。

1.service映射

@Resource
private TypeDao typeDao;public PageInfo<Book> findBySearch(Params params) {// 开启分页查询PageHelper.startPage(params.getPageNum(), params.getPageSize());// 接下来的查询会自动按照当前开启的分页设置来查询List<Book> list = bookDao.findBySearch(params);if (CollectionUtil.isEmpty(list)) {return PageInfo.of(new ArrayList<>());}for (Book book : list) {if (ObjectUtil.isNotEmpty(book.getTypeId())) {Type type = typeDao.selectByPrimaryKey(book.getTypeId());if (ObjectUtil.isNotEmpty(type)) {book.setTypeName(type.getName());}}}return PageInfo.of(list);
}

2.mapper关联

select book.*,type.name as typeName from book left join type on book.typeId=type.id

10.角色管理

这里的一个简便方法就是,首先拿到localstorage里面的user,然后用if语句判断用户的role 是否是你想要的角色,就可以隐藏显示menu控件等。

v-if="user.role === 'ROLE_ADMIN'">
const user=ref(localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {})
return{user,}

11.审批功能

这个功能是在一个模块里面完成,一个角色负责申请(add),另一个角色负责审批(update/edit)。
这里面主要是有两个dialog-form;然后就是当学生打开dialog时,自动拿到他的id(form.value.userId =user.value.id;),放到表单一起提交到后台,然后后台在显示列表时,加一个条件就是id和学生限制,这样每个学生就只能看到自己的申请记录:

if ("ROLE_STUDENT".equals(user.getRole())) {params.setUserId(user.getId());
}
<select id="findBySearch" resultType="com.hckj.springboot.entity.Audit">select audit.*, admin.name as userName from audit left join admin on audit.userId = admin.id<where><if test="params != null and params.name != null and params.name != ''">and audit.name like concat('%', #{ params.name }, '%')</if><if test="params != null and params.userId != null">and audit.userId = #{ params.userId }</if></where>
</select>

12.预约功能

这个功能涉及到两个模块,一个模块负责酒店信息列表和预约功能,一个模块负责显示预约列表。所以有两个表和实体类,hotel信息和reserve信息。reserve信息涉及将id转换为name,这个主要就是现在entity里面用transient注解,然后在mapper或者service层过滤。

@Column(name = "hotelId")
private Integer hotelId;
@Column(name = "userId")
private Integer userId;
@Transient
private String hotelName;
@Transient
private String userName;

13.AOP日志管理

日志管理的前端和后端对数据库的增删差都和前面没差,主要就是要实现AOP切面管理。

1.依赖

首先导入要用的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.自定义注解

在common/AutoLog.java里面自定义一个注解,这个注解将被用在controller的方法上。

package com.hckj.springboot.common;
import java.lang.annotation.*;
@Target(ElementType.METHOD)//指定注解可以应用的目标元素,这里是 ElementType.METHOD,表示该注解可以用于方法。
@Retention(RetentionPolicy.RUNTIME)//指定注解的生命周期,RetentionPolicy.RUNTIME 表示该注解会在运行时保留,这允许在运行时通过反射来访问注解信息
@Documented//指定了注解 AutoLog 包含在生成的 Javadoc 文档中
public @interface AutoLog {String value() default "";
}

3.AOP切面处理

在common/LogAspect.java里面将使用控制器方法前后需要做的动作定义好。

@Component
@Aspect // 表示 LogAspect 类是一个切面类,用于定义横切关注点(cross-cutting concerns),在这里是用于日志记录。
public class LogAspect {@Resourceprivate LogService logService;@Around("@annotation(autoLog)")//使用 @Around 注解指定在目标方法执行前和执行后都会执行的通知。@annotation(autoLog) 表示这个通知会织入那些被标记了@AutoLog 注解的方法。public Object doAround(ProceedingJoinPoint joinPoint,AutoLog autoLog)throws Throwable{//joinPoint 是Spring AOP提供的一个接口,用于访问被通知方法的信息。String name = autoLog.value();//在注解里定义了value()String time = DateUtil.now();// 操作时间(当前时间)String username = ""; 操作人Admin user = JwtTokenUtils.getCurrentUser();if (ObjectUtil.isNotNull(user)) {username = user.getName();}HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();//通过RequestContextHolder获取当前请求的上下文信息,然后,执行了被通知方法,获取了方法的返回结果 Result。String ip = request.getRemoteAddr();// 操作人IP//前面是切面前执行Result result = (Result) joinPoint.proceed();// 执行具体的接口(开始去执行注解的方法的内容)//后面是切面后执行Object data = result.getData();if (data instanceof Admin) {//登录操作,没有从token中拿到name,所以接口执行完了再那name。Admin admin = (Admin) data;username = admin.getName();}Log log = new Log(null, name, time, username, ip);//去往日志表里写一条日志记录,admin实体类要有构造方法logService.add(log);return result;};
}

4.在controller的方法里面使用自定义的注解

@AutoLog("登录")
@AutoLog("酒店预订")

14.图形验证码

首先是前端随机生成一个key,然后发送到后端,后端用着key生成一个value(验证码数据)和图片,然后把图片发送到前端,让后登录按钮点击后会再次带上这个key,后台会根据key找value,看和前端发过来的数字是否一致。

1.依赖

<dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version>
</dependency>

2.定义Mapper映射格式

因为涉及到key,value所以在common/CaptureConfig.java里面定义一个captureconfig类,他的格式就是map映射的格式

@Component
public class CaptureConfig {public static Map<String ,String > CAPTURE_MAP=new HashMap<>();
}

3.生成验证码的控制器

在controller/CaptureController.java里面根据key生成value和验证码图片

@CrossOrigin
@RestController
@RequestMapping
public class CaptureController {@RequestMapping("/captcha")public void captcha(@RequestParam String key, HttpServletRequest request, HttpServletResponse response) throws Exception {// 指定验证码的长宽以及字符的个数SpecCaptcha captcha = new SpecCaptcha(135, 33, 5);captcha.setCharType(Captcha.TYPE_NUM_AND_UPPER);// 首先把验证码在后台保存一份,但是不能保存在session,可以存在redis,也可以存在后台的某个Map里面CaptureConfig.CAPTURE_MAP.put(key, captcha.text().toLowerCase());CaptchaUtil.out(captcha, request, response);// 算术类型
//        ArithmeticCaptcha captcha = new ArithmeticCaptcha(135, 33);
//        captcha.setLen(4);  // 几位数运算,默认是两位
//        captcha.getArithmeticString();  // 获取运算的公式:3+2=?
//        captcha.text();  // 获取运算的结果:5
//        CaptureConfig.CAPTURE_MAP.put(key, captcha.text().toLowerCase());
//        CaptchaUtil.out(captcha, request, response);}
}

4.登陆页面的key和验证码请求

这里提前做两件事儿,首先是在admin实体类里面添加临时数据

 @Transient
private String verCode;

2.访问captcha控制器没有token,所以需要在webconfig里面放行:.excludePathPatterns("/api/captcha")
3.现在就可以开始在前端生成key,发送给后端captcha_controller生成验证码图像,然后登录时给请求地址里添加key

<el-form-item><div style="display: flex; justify-content: center; align-items: center;"><el-input v-model="admin.verCode" prefix-icon="el-icon-user" style="width: 60%;" placeholder="请输入验证码"></el-input><img :src="captchaUrl" @click="clickImg()" style="cursor: pointer; width:140px; height:33px" /></div>
</el-form-item>const admin=ref({name:'',password:'',verCode: '',});const key=ref("");const captchaUrl=ref("");const clickImg = () => {key.value = Math.random();captchaUrl.value = `http://localhost:8181/api/captcha?key=${key.value}`;};
onMounted(()=>{key.value=Math.random();captchaUrl.value = 'http://localhost:8181/api/captcha?key=' + key.value;
});

5.后端登录的验证

现在需要拿到请求路径力的key,然后根据map映射拿到原本的captcha和用户提交的form表单里的captcha进行验证。

 @PostMapping("/login")@AutoLog("登录")public Result login(@RequestBody Admin admin,@RequestParam String key, HttpServletRequest request){if (!admin.getVerCode().toLowerCase().equals(CaptureConfig.CAPTURE_MAP.get(key))) {// 如果不相等,说明验证不通过CaptchaUtil.clear(request);return Result.error("验证码不正确");}Admin loginUser=adminService.login(admin);return Result.success(loginUser);}

15.Echarts

可以去echarts官网进行学习,首先下载导入

npm install echarts
import * as echarts from 'echarts';

然后利用官网文档作图,这里需要注意的时图的初始化initECharts和后台数据的处理。

1.饼状图

bie图的数据格式是[{value:xxx,name:xxx},{}],所以后端传递的数据要处理成这种格式:

@Select("select book.*, type.name as typeName from book left join type on book.typeId = type.id")
List<Book> findAll();
public List<Book> findAll(){return bookDao.findAll();}
@GetMapping("/echarts/bie")
public Result bie() {// 查询出所有图书List<Book> list = bookService.findAll();Map<String, Long> collect = list.stream().filter(x -> ObjectUtil.isNotEmpty(x.getTypeName())).collect(Collectors.groupingBy(Book::getTypeName, Collectors.counting()));// 最后返回给前端的数据结构List<Map<String, Object>> mapList = new ArrayList<>();if (CollectionUtil.isNotEmpty(collect)) {for (String key : collect.keySet()) {Map<String, Object> map = new HashMap<>();map.put("name", key);map.put("value", collect.get(key));mapList.add(map);}}return Result.success(mapList);
}

前端的话就是给一个div表明位置,然后准备初始化数据,并都放在initecharts,最后挂载到onmounted上,再return。

<div id="bie" style="width: 100%; height: 400px"></div>
const initBie=(data)=>{var chartDom = document.getElementById('bie');let myChart = echarts.init(chartDom);const option = {title: {text: '图书统计(饼图)',subtext: '统计维度:图书分类',left: 'center'},tooltip: {trigger: 'item'},legend: {orient: 'vertical',left: 'left'},series: [{name: 'Access From',type: 'pie',radius: '50%',data: data,emphasis: {itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]};option && myChart.setOption(option);
};
const initEcharts=()=>{request.get("/book/echarts/bie").then(res => {if (res.code === '0') {// 开始去渲染饼图数据啦initBie(res.data)}})
};onMounted(()=>{initEcharts();});

2.折线图和柱状图

这两个图的数据格式是一样的

@GetMapping("/echarts/bar")
public Result bar() {// 查询出所有图书List<Book> list = bookService.findAll();Map<String, Long> collect = list.stream().filter(x -> ObjectUtil.isNotEmpty(x.getTypeName())).collect(Collectors.groupingBy(Book::getTypeName, Collectors.counting()));List<String> xAxis = new ArrayList<>();List<Long> yAxis = new ArrayList<>();if (CollectionUtil.isNotEmpty(collect)) {for (String key : collect.keySet()) {xAxis.add(key);yAxis.add(collect.get(key));}}Map<String, Object> map = new HashMap<>();map.put("xAxis", xAxis);map.put("yAxis", yAxis);return Result.success(map);
}

前端同上

const initBie=(data)=>{var chartDom = document.getElementById('bie');let myChart = echarts.init(chartDom);const option = {title: {text: '图书统计(饼图)',subtext: '统计维度:图书分类',left: 'center'},tooltip: {trigger: 'item'},legend: {orient: 'vertical',left: 'left'},series: [{name: 'Access From',type: 'pie',radius: '50%',data: data,emphasis: {itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]};option && myChart.setOption(option);
};
const initBar=(xAxis, yAxis)=>{let chartDom = document.getElementById('bar');let myChart = echarts.init(chartDom);let option;option = {title: {text: '图书统计(柱状图)',subtext: '统计维度:图书分类',left: 'center'},xAxis: {type: 'category',data: xAxis},yAxis: {type: 'value'},series: [{data: yAxis,type: 'bar',showBackground: true,backgroundStyle: {color: 'rgba(180, 180, 180, 0.2)'}}]};option && myChart.setOption(option);
};
const initEcharts=()=>{request.get("/book/echarts/bar").then(res => {if (res.code === '0') {// 开始去渲染柱状图数据啦initBar(res.data.xAxis, res.data.yAxis)// 开始去渲染折线图数据啦initLine(res.data.xAxis, res.data.yAxis)}})
};

16.富文本

1.首先下载并导入wangeditor,前端export之前初始化富文本:

npm i wangeditor --save
import E from 'wangeditor'
let editor
function initWangEditor(content) {	setTimeout(() => {if (!editor) {editor = new E('#editor')editor.config.placeholder = '请输入内容'editor.config.uploadFileName = 'file'editor.config.uploadImgServer = 'http://localhost:8181/api/files/wang/upload'editor.create()}editor.txt.html(content)
}, 0)
}

2.后端这里就是添加一列content,然后实体类也添加,然后一个富文本编辑器的文件上传功能,因为这里会有图片之类的文件

/*** wang-editor编辑器文件上传接口*/
@PostMapping("/wang/upload")
public Map<String, Object> wangEditorUpload(MultipartFile file) {String flag = System.currentTimeMillis() + "";String fileName = file.getOriginalFilename();try {// 文件存储形式:时间戳-文件名FileUtil.writeBytes(file.getBytes(), filePath + flag + "-" + fileName);System.out.println(fileName + "--上传成功");Thread.sleep(1L);} catch (Exception e) {System.err.println(fileName + "--文件上传失败");}Map<String, Object> resMap = new HashMap<>();// wangEditor上传图片成功后, 需要返回的参数resMap.put("errno", 0);resMap.put("data", CollUtil.newArrayList(Dict.create().set("url", "http://localhost:8080/api/files/" + flag)));return resMap;
}

3.首先是在el-table里面添加一列按钮,列表是图书介绍,按钮显示点击查看。

 <el-table-column label="图书介绍"><template v-slot="scope"><el-button type="success" @click="viewEditor(scope.row.content)">点击查看</el-button></template>
</el-table-column>

4.当点击查看时就显示一个dialogue,里面是图书介绍的html的渲染结果:

<el-dialog title="图书介绍" v-model="editorVisible" width="50%"><div v-html="this.viewData" class="w-e-text"></div>
</el-dialog>
const viewData=ref('');
const editorVisible=ref(false);
const viewEditor=(data)=> {viewData.value = data;editorVisible.value = true;
};

5.然后就是给add和eddit时的对话框添加富文本编辑器(id="editor"),提交form之前先给form里面添加content内容。

<el-form-item label="图书介绍" label-width="15%"><div id="editor" style="width: 90%"></div>
</el-form-item>
const add=()=>{form.value={};initWangEditor("");dialogFormVisible.value=true;
};
const edit=(obj)=>{form.value=obj;initWangEditor(obj.content ? form.value.content : "");dialogFormVisible.value=true;
}
const submit=()=>{form.value.content = editor.txt.html();request.post('book/addedit',form.value).then((res)=>{if (res.code==="0"){dialogFormVisible.value=false;findBySearch();}})
}

效果

在这里插入图片描述

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

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

相关文章

Grafana如何实现折线柱状图

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

竞赛选题 车位识别车道线检测 - python opencv

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 车位识别车道线检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) …

从六个方面对比Go和Python的差异

您是否想过 Go 与 Python 之间的主要区别是什么&#xff1f;随着对软件开发人员的需求不断增加&#xff0c;选择哪种编码语言可能会很困难。 ​ 在此&#xff0c;我们将从六个方面对比Go和Python,探讨 Go 和 Python之间的差异。我们将讨论它们的特点、优缺点&#xff0c;以便…

GPT、GPT-2、GPT-3论文精读笔记

视频&#xff1a;GPT&#xff0c;GPT-2&#xff0c;GPT-3 论文精读【论文精读】_哔哩哔哩_bilibili MAE论文&#xff1a;把bert用回计算机视觉领域 CLIP论文&#xff1a;打通文本和图像 GPT 论文&#xff1a;Improving Language Understanding by Generative Pre-Training …

史诗级云故障敲响警钟,应用保障不能没有“连续键”!

近日&#xff0c;知名云服务商出现一次史诗级的云故障&#xff1a;全球所有区域/所有服务同时异常&#xff0c;故障持续长达3小时之多&#xff0c;云上众多应用受到极大影响。 如今&#xff0c;在一个充满不确定性和复杂性的数字化时代&#xff0c;哪怕是顶级云服务商亦不能避…

并行与分布式计算 第9章 算法设计

文章目录 并行与分布式计算 第9章 算法设计9.1 设计过程9.1.1 PCAM设计过程9.1.2 划分9.1.3 通信9.1.4 组合9.1.5 映射 8.2 设计方法8.2.1 划分技术9.2.2 分治9.2.3 平衡树技术9.2.4倍增技术9.2.5 流水线技术9.2.6 破对称技术 并行与分布式计算 第9章 算法设计 9.1 设计过程 …

一张图,了解美格智能高算力AI模组

美格智能高算力A模组&#xff0c;澎湃算力让AI触手可及&#xff01;

数字化背景下,集流体行业的智能制造方法论

行业背景 随着全球对清洁能源需求的不断增加&#xff0c;新能源领域正在迅速崛起&#xff0c;在新能源技术中&#xff0c;锂电池作为一种高效、轻便的能量储存解决方案&#xff0c;正成为主流。而锂电集流体作为锂电池的核心部件&#xff0c;承担着电池内部电流分布的关键角色…

服务号可以迁移到订阅号吗

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;首先我们要看一下服务号和订阅号的主要区别。1、服务号推送的消息没有折叠&#xff0c;消息出现在聊天列表中&#xff0c;会像收到消息一样有提醒。而订阅号推送的消息是折叠的&#xff0c;“订阅号…

分布式篇---第二篇

系列文章目录 文章目录 系列文章目录前言一、你知道哪些分布式事务解决方案?二、什么是二阶段提交?三、什么是三阶段提交?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你…

基于Pytorch框架多人多摄像头摔倒跌倒坠落检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习在计算机视觉领域的应用已经取得了显著的进展&#xff0c;特别是在多人多摄像头场景下的摔倒跌倒检测。通过…

Vue环境的搭建

1.Vue开发的两种方式 &#xff08;1&#xff09;核心包传统开发模式 基于html/css/js文件&#xff0c;直接引入和辛堡&#xff0c;开发Vue。 &#xff08;2&#xff09;工程化开发模式&#xff1a; 主要是基于构建工具&#xff08;例如,webpack&#xff09;的环境中开发Vue…

福州大学《嵌入式系统综合设计》实验五:图像裁剪及尺寸变换

一、实验目的 在深度学习中&#xff0c;往往需要从一张大图中裁剪出一张张小图&#xff0c;以便适应网络输入图像的尺寸&#xff0c;这可以通过bmcv_image_crop函数实现。 实践中&#xff0c;经常需要对输入图像的尺寸进行调整&#xff0c;以适用于网络输入图片尺寸&#xff0…

查看sql语句执行计划并重建索引

晚上cpu报警显示当前cpu使用率达到90%以上&#xff0c;不到10%的空闲 先查询当前sql&#xff1a; #&#xff08;ORACLE&#xff09; SQL > set line 200 pages 1000 col event for a30 col program for a30 col username for a12 col sql_id for a15 col INST_ID for 9999 …

前端开发学习 (二) 事件修饰符、系统命令

其实&#xff0c;我们上一章的时候就已经说过了一些系统指令&#xff0c;这里详细介绍一下 一、v-on的事件修饰符 事件作用click点击时触发submit表单被提交时触发input输入框发生改变时触发keyup按键松开时触发keydown按键按下时触发mouseover鼠标悬停触发mouseout当鼠标移开…

安徽省广德市选择云轴科技ZStack Cloud云平台建设县级智慧城市

信创是数字中国建设的重要组成部分&#xff0c;也是数字经济发展的关键推动力量。作为云基础软件企业&#xff0c;云轴科技ZStack产品矩阵全面覆盖数据中心云基础设施&#xff0c;ZStack信创云首批通过可信云《一云多芯IaaS平台能力要求》先进级&#xff0c;是其中唯一兼容四种…

【Web】NewStarCTF Week1 个人复现

目录 ①泄露的秘密 ②Begin of Upload ③Begin of HTTP ④ErrorFlask ⑤Begin of PHP ⑥R!C!E! ⑦EasyLogin ①泄露的秘密 盲猜/robots.txt,访问得到flag前半部分 第二个没试出来&#xff0c;老老实实拿dirsearch扫吧 访问/www.zip 下载附件&#xff0c;拿到第二部分…

SpringCloud原理-OpenFeign篇(一、Hello OpenFeign项目示例)

文章目录 前言正文一、项目结构二、服务调用链路说明三、Rpc调用链路说明四、项目代码4.1 client 模块中的feign接口4.2 client 中的rest接口4.3 client 中的启动类4.4 server中的rest接口4.5 server中的配置文件 五、调试 附录附1&#xff1a;本系列文章链接 前言 本篇是Spri…

示波器探头讲解及案例分享

示波器探头讲解 示波器探头 分为X1、X10档&#xff1a; X1档&#xff0c;表示被测量的信号没有经过衰减进入示波器 X10档&#xff0c;表示被测量的信号衰减10倍进入示波器&#xff08;当示波器也设置为10X档&#xff0c;直接读数即可&#xff0c;但是当示波器设置为1X档&…

1、开发工具介绍及软件安装

软件安装 百度网盘链接&#xff1a;https://pan.baidu.com/s/1J7sgXntt9eLNBhI1Dc6MsQ?pwd2023 提取码&#xff1a;2023 # 一 Keil5安装步骤&#xff1a; 激活&#xff1a; STC-ISP安装步骤&#xff1a; STC-ISP.exe安装即用 安装CH340_CH341驱动程序 所有学习课程和资源…