springboot+mybatis+mybatis-plus对crud项目进行改进

springboot+mybatis实现简单的增、删、查、改https://blog.csdn.net/heyl163_/article/details/132197201上一篇文章,已经详细地介绍了怎么通过springboot项目整合mybatis实现简单的数据库表的增删改查功能,是最简单的springboot项目的结构。所以有很多问题,包括简单sql语句需要重复编写、数据校验、异常处理、操作类方法没有返回响应等等。这篇文章我们通过使用springmvc的全局异常处理机制以及引入mybatis-plus和validation来解决这几个问题。

基于上一篇文章的项目,新建一个分支springboot1.0,保存最新的代码。

一、简单sql语句重复编写问题

通过引入mybatis-plus来解决重复编写简单sql语句的问题。

在pom.xml中引入mybatis-plus的依赖

<properties>
    <mybatis-plus.version>3.5.1</mybatis-plus.version>
</properties>

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>

然后SongMapper继承BaseMapper接口,然后就能调用BaseMapper里预先定义的crud方法了。

package com.example.springboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springboot.entity.Song;
import org.springframework.stereotype.Repository;

/**
 * @author heyunlin
 * @version 1.0
 */
@Repository
public interface SongMapper extends BaseMapper<Song> {

}

BaseMapper的参数类型为对应实体类的类型;

 

因为BaseMapper里有几个方法selectById()、deleteById()、updateById()需要用到表的主键,因此,在实体类上需要通过注解@TableId来标注哪个字段是表的主键;

 

如果数据库表名和实体类的类名不一致,需要通过@TableName注解指明对应的数据库表;

 

如果数据库表名的字段名和实体类的属性名不一致,需要通过@TableField注解指明对应的数据库表的字段;

package com.example.springboot.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 歌曲
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("song")
public class Song implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(type = IdType.INPUT)
    private String id;

    /**
     * 歌曲名
     */
    @TableField("name")
    private String name;

    /**
     * 歌手
     */
    @TableField("singer")
    private String singer;

    /**
     * 描述信息
     */
    @TableField("note")
    private String note;

    /**
     * 最后一次修改时间
     */
    @TableField("last_update_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime lastUpdateTime;
}

然后,删除SongMapper.java中编写的方法和对应SongMapper.xml中的statement

<?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.example.springboot.mapper.SongMapper">
    
</mapper>

删除之后惊奇地发现service中调用mapperde的方法并没有报错,因为这些方法已经预定义在BaseMapper中了。

mybatis-plus使用简单,归根于BaseMapper中预先帮我们实现的方法,接下来详细介绍这些方法的作用。

package com.baomidou.mybatisplus.core.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;

public interface BaseMapper<T> extends Mapper<T> {
    // 添加,如果字段值为null,则不会设置到insert语句中
    int insert(T entity);

    // 通过id删除,id字段的类型需要实现Serializable接口
    int deleteById(Serializable id);

    int deleteById(T entity);

    // 条件删除,把条件放到Map中,map的key是数据库表的字段名,map的value是字段的值
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);

    // 条件删除,通过条件构造器来设置条件
    int delete(@Param("ew") Wrapper<T> queryWrapper);

    // 通过ID批量删除,相当于delete from xxx where id in idList
    int deleteBatchIds(@Param("coll") Collection<?> idList);

    // 通过id修改,如果字段值为null,则不会设置到update语句中
    int updateById(@Param("et") T entity);

    // 条件修改,通过条件构造器来设置条件,entity中的数据为Wrapper的set()方法设置的字段值
    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

    // 通过id查询,id字段的类型需要实现Serializable接口
    T selectById(Serializable id);

    // 通过ID批量查询,相当于select * from xxx where id in idList
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    // 条件查询,通过条件构造器来设置查询条件
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);

    // 条件查询,通过条件构造器来设置查询条件
    // 如果确定查询最多只有一行结果,可以使用该方法,否则将报错
    default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {
        List<T> ts = this.selectList(queryWrapper);
        if (CollectionUtils.isNotEmpty(ts)) {
            if (ts.size() != 1) {
                throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records", new Object[0]);
            } else {
                return ts.get(0);
            }
        } else {
            return null;
        }
    }

    // existes语句
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0L;
    }

    // 条件查询记录数,通过条件构造器来设置查询条件
    Long selectCount(@Param("ew") Wrapper<T> queryWrapper);

    // 条件查询,通过条件构造器来设置查询条件
    // 当查询结果有多条时,不能使用selectOne,需要使用该方法
    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);

    // 分页查询
    <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);

    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
}

 

二、数据校验问题

1、ID不能为空的问题

delete()和detail()方法理论上需要ID不为空,否则查询结果必然是空,为了避免无效的查询,应该设置id不能为空,通过@RequestParam(requied = true)来限制id不能为空

SongController.java对应修改

@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public void delete(@RequestParam(required = true) @PathVariable("id") String id) {
    songService.delete(id);
}
@RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
public Song detail(@RequestParam(required = true) @PathVariable("id") String id) {
    return songService.detail(id);
}

如下图,当通过postman测试接口时,不传id会报错

cc8c86b7b65f4c908e0bb4616d3c8f0e.png

传正确的ID时,成功返回了查询结果。

0e911367c75249babdf03f6fe2761d03.png

 

2、添加校验

通过validation数据校验框架完成数据校验。

在pom.xml中引入validation的依赖

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

然后创建一个对应前端传来的数据的dto对象,比如添加对应一个dto对象,用户只需要输入name、singer和note三个字段的值,而且name、singer这两个字段是必填的,也就是需要验证非空(ID是通过当前时间生成的)。

10579a2651764c7eb8376c8f57e3b840.png

 添加时的dto对象

package com.example.springboot.dto;

import lombok.Data;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongInsertDTO {
    /**
     * 歌曲名
     */
    private String name;

    /**
     * 歌手
     */
    private String singer;

    /**
     * 描述信息
     */
    private String note;
}

接着,在字段上使用validation定义的注解对字段值进行校验,校验失败会抛出BindException异常,然后对异常进行处理即可,详情请参考文章:

validation数据校验框架https://blog.csdn.net/heyl163_/article/details/132112153

SongInsertDTO.java

package com.example.springboot.dto;

import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongInsertDTO {
    /**
     * 歌曲名
     */
    @NotNull(message = "歌曲名不能为空")
    @NotEmpty(message = "歌曲名不能为空")
    private String name;

    /**
     * 歌手
     */
    @NotNull(message = "歌手不能为空")
    @NotEmpty(message = "歌手不能为空")
    private String singer;
}

然后控制器SongController上的insert()方法的参数类型改为SongInsertDTO,然后在参数上添加@Valid或@Validated注解

@RequestMapping(value = "/insert", method = RequestMthod.POST)
public void insert(@Validated SongInsertDTO insertDTO) {
        songService.insert(insertDTO);
}

相应的,service也要修改

SongService

void insert(SongInsertDTO insertDTO);

SongServiceImpl 

@Override
public void insert(SongInsertDTO insertDTO) {
    Song song = new Song();

    song.setId(StringUtils.uuid());
    song.setName(insertDTO.getName());
    song.setSinger(insertDTO.getSinger());
    
    if (StringUtils.isNotEmpty(insertDTO.getNote())) {
        song.setNote(insertDTO.getNote());
    }

    songMapper.insert(song);
}

 

3、修改校验

修改时需要ID、name、singer不为空,则创建一个SongUpdateDTO类即可,然后按照相同的步骤在类的属性上和控制器方法的参数上添加对应的注解即可。

package com.example.springboot.dto;

import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
 * @author heyunlin
 * @version 1.0
 */
@Data
public class SongUpdateDTO {

    /**
     * 歌曲编号/ID
     */
    @NotNull(message = "歌曲编号不能为空")
    @NotEmpty(message = "歌曲编号不能为空")
    private String id;
    
    /**
     * 歌曲名
     */
    @NotNull(message = "歌曲名不能为空")
    @NotEmpty(message = "歌曲名不能为空")
    private String name;

    /**
     * 歌手
     */
    @NotNull(message = "歌手不能为空")
    @NotEmpty(message = "歌手不能为空")
    private String singer;

    /**
     * 描述信息
     */
    private String note;
}

控制器SongController上的update()方法的参数类型改为SongUpdateDTO,然后在参数上添加@Valid或@Validated注解 

@RequestMapping(value = "/update", method = RequestMethod.POST)
public void update(@Valid SongUpdateDTO updateDTO) {
    songService.update(updateDTO);
}

相应的,service也要修改 

SongService

void update(SongUpdateDTO updateDTO);

SongServiceImpl 

@Override
public void update(SongUpdateDTO updateDTO) {
    Song song = new Song();

    song.setId(StringUtils.uuid());
    song.setName(updateDTO.getName());
    song.setSinger(updateDTO.getSinger());

    if (StringUtils.isNotEmpty(updateDTO.getNote())) {
        song.setNote(updateDTO.getNote());
    }
    song.setLastUpdateTime(LocalDateTime.now());

    songMapper.updateById(song);
}

 

三、操作类方法没有响应数据问题

无论操作成功还是失败,都应该向客户端返回操作的结果。

1、创建响应对象

新建一个响应对象实体类,包含状态码、数据和响应的消息(提示)。

package com.example.springboot.restful;

import lombok.Data;

@Data
public class JsonResult<T> {

    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应提示信息
     */
    private String message;
    /**
     * 响应数据
     */
    private T data;

    public static JsonResult<Void> success() {
        return success(null);
    }

    public static JsonResult<Void> success(String message) {
        return success(message, null);
    }

    public static <T> JsonResult<T> success(String message, T data) {
        JsonResult<T> jsonResult = new JsonResult<>();

        jsonResult.setCode(ResponseCode.OK.getValue());
        jsonResult.setMessage(message);
        jsonResult.setData(data);

        return jsonResult;
    }

    public static JsonResult<Void> error(String message) {
        JsonResult<Void> jsonResult = new JsonResult<>();

        jsonResult.setCode(ResponseCode.ERROR.getValue());
        jsonResult.setMessage(message);

        return jsonResult;
    }

    public static JsonResult<Void> error(ResponseCode responseCode, Throwable e) {
        return error(responseCode, e.getMessage() != null ? e.getMessage() : "系统发生异常,请联系管理员!");
    }

    public static JsonResult<Void> error(ResponseCode responseCode, String message) {
        JsonResult<Void> jsonResult = new JsonResult<>();

        jsonResult.setCode(responseCode.getValue());
        jsonResult.setMessage(message);

        return jsonResult;
    }

}

 

2、定义响应状态码

package com.example.springboot.restful;

/**
 * 响应状态码
 * @author heyunlin
 * @version 1.0
 */
public enum ResponseCode {
    
    /**
     * 请求成功
     */
    OK(200),
    /**
     * 失败的请求
     */
    BAD_REQUEST(400),
    /**
     * 未授权
     */
    UNAUTHORIZED(401),
    /**
     * 禁止访问
     */
    FORBIDDEN(403),
    /**
     * 找不到
     */
    NOT_FOUND(404),
    /**
     * 不可访问
     */
    NOT_ACCEPTABLE(406),
    /**
     * 冲突
     */
    CONFLICT(409),
    /**
     * 服务器发生异常
     */
    ERROR(500);

    private final Integer value;

    ResponseCode(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }

}

 

3、增删改方法添加返回值

insert()、update()、delete()三个方法的返回值类型修改为JsonResult<Void>,JsonResult<T>的参数类型T表示返回的数据的类型,在这里不需要返回数据,只需要返回给用户看的提示信息。

SongController.java

package com.example.springboot.controller;

import com.example.springboot.dto.SongInsertDTO;
import com.example.springboot.dto.SongUpdateDTO;
import com.example.springboot.entity.Song;
import com.example.springboot.restful.JsonResult;
import com.example.springboot.service.SongService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * @author heyunlin
 * @version 1.0
 */
@RestController
@RequestMapping(path = "/song", produces="application/json;charset=utf-8")
public class SongController {

    private final SongService songService;

    @Autowired
    public SongController(SongService songService) {
        this.songService = songService;
    }

    @RequestMapping(value = "/insert", method = RequestMethod.POST)
    public JsonResult<Void> insert(@Validated SongInsertDTO insertDTO) {
        songService.insert(insertDTO);

        return JsonResult.success("添加成功");
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
    public JsonResult<Void> delete(@RequestParam(required = true) @PathVariable("id") String id) {
        songService.delete(id);

        return JsonResult.success("删除成功");
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public JsonResult<Void> update(@Valid SongUpdateDTO updateDTO) {
        songService.update(updateDTO);

        return JsonResult.success("修改成功");
    }

    @RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
    public Song detail(@RequestParam(required = true) @PathVariable("id") String id) {
        return songService.detail(id);
    }

}

这样的话,用户操作添加、删除、修改歌曲时,能得到响应,成功或者失败,以及失败的原因。

 

4、深入分析操作影响行数 

当然了,这里只要没报错就默认操作成功了,如果要求完美,可以获取BaseMapper的增删改方法的返回值,这个返回值是int类型,表示本次操作受影响的行数:插入行数、删除行数、修改行数,根据这个行数返回对应的提示给用户即可。

对应SongServiceImpl的修改

package com.example.springboot.service.impl;

import com.example.springboot.dto.SongInsertDTO;
import com.example.springboot.dto.SongUpdateDTO;
import com.example.springboot.entity.Song;
import com.example.springboot.exception.GlobalException;
import com.example.springboot.mapper.SongMapper;
import com.example.springboot.restful.ResponseCode;
import com.example.springboot.service.SongService;
import com.example.springboot.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * @author heyunlin
 * @version 1.0
 */
@Service
public class SongServiceImpl implements SongService {

    private final SongMapper songMapper;

    @Autowired
    public SongServiceImpl(SongMapper songMapper) {
        this.songMapper = songMapper;
    }

    @Override
    public void insert(SongInsertDTO insertDTO) {
        Song song = new Song();

        song.setId(StringUtils.uuid());
        song.setName(insertDTO.getName());
        song.setSinger(insertDTO.getSinger());

        if (StringUtils.isNotEmpty(insertDTO.getNote())) {
            song.setNote(insertDTO.getNote());
        }

        int rows = songMapper.insert(song);
        
        if (rows < 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "添加时发生了异常,预计影响" + rows + "条记录,已经对本次操作影响的数据进行恢复");
        }
    }

    @Override
    public void delete(String id) {
        int rows = songMapper.deleteById(id);

        if (rows == 0) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "删除失败,本次操作删除了" + rows + "条记录。");
        } else if (rows > 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "删除失败,操作过程中发生了不可预知的错误。");
        }
    }

    @Override
    public void update(SongUpdateDTO updateDTO) {
        Song song = new Song();

        song.setId(StringUtils.uuid());
        song.setName(updateDTO.getName());
        song.setSinger(updateDTO.getSinger());

        if (StringUtils.isNotEmpty(updateDTO.getNote())) {
            song.setNote(updateDTO.getNote());
        }
        song.setLastUpdateTime(LocalDateTime.now());

        int rows = songMapper.updateById(song);

        if (rows == 0) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "修改失败,本次操作删除了" + rows + "条记录。");
        } else if (rows > 1) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "修改失败,操作过程中发生了不可预知的错误。");
        }
    }

    @Override
    public Song detail(String id) {
        return songMapper.selectById(id);
    }

}

 

四、异常处理问题

程序的健壮性作为3大重要指标,可见非常重要,当程序发生异常时,应该被很好的处理,而不是让用户看到500的页面,或者长时间无响应。

这篇文章通过springmvc统一异常处理机制来解决这个问题,不需要在每个方法上使用try...catch...finally来手动处理异常。

首先,创建一个自定义异常,继承RuntimeException,这时候可能你就会好奇:为什么是继承RuntimeException呢?能不能继承其他Exception,答案是可以。

但是这里需要了解检查异常和非检查异常的区别:

非检查异常:RuntimeException及其子类异常,不需要手动处理,也就是不需要通过try...catch捕获或者通过throws关键字在方法上面申明抛出异常。

检查异常:除了RuntimeException及其子类型异常以外的异常都是检查异常,必须手动处理,通过try...catch捕获或者通过throws关键字在方法上面申明抛出异常。如果不处理,编译就会报错。

所以,我们使用非检查异常的好处显而易见,当我们在代码里主动使用throw关键字抛出RuntimeException异常时不需要去处理就可以通过编译。

我们创建一个exception子包来存放异常相关的类,在该类下创建一个全局异常类GlobalException

package com.example.springboot.exception;

import com.example.springboot.restful.ResponseCode;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 自定义异常
 * @author heyunlin
 * @version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class GlobalException extends RuntimeException {
    private ResponseCode responseCode;

    public GlobalException(ResponseCode responseCode, String message) {
        super(message);

        setResponseCode(responseCode);
    }

}

然后在当前的exception包下面创建一个handler,然后在handler包下创建一个统一异常处理类

package com.example.springboot.exception.handler;

import com.example.springboot.exception.GlobalException;
import com.example.springboot.restful.JsonResult;
import com.example.springboot.restful.ResponseCode;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * 全局异常处理类
 * @author heyunlin
 * @version 1.0
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理GlobalException
     * @param e GlobalException
     * @return JsonResult<Void>
     */
    @ExceptionHandler(GlobalException.class)
    public JsonResult<Void> handlerGlobalException(HttpServletResponse response, GlobalException e) {
        e.printStackTrace();
        response.setStatus(e.getResponseCode().getValue());

        return JsonResult.error(e.getResponseCode(), e);
    }

    /**
     * 处理BindException
     * @param e BindException
     * @return JsonResult<Void>
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public JsonResult<Void> handlerBindException(BindException e) {
        e.printStackTrace();

        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

        return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage);
    }

    /**
     * 处理Exception
     * @param e Exception
     * @return JsonResult<Void>
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult<Void> handlerException(Exception e) {
        e.printStackTrace();

        return JsonResult.error(ResponseCode.ERROR, e);
    }

}

这样的话,当发生对应的异常时,会执行对应的方法,比如数据校验失败时发生了BindException,会执行以下方法

/**
 * 处理BindException
 * @param e BindException
 * @return JsonResult<Void>
 */
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public JsonResult<Void> handlerBindException(BindException e) {
        e.printStackTrace();

 

        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

 

        return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage);
}

通过上面几个步骤,项目的结构已经相对完善了。

 

好了,这篇文章就分享到这里了,看完不要忘了点赞+收藏哦~

源码已经上传至git,按需获取:

springboot整合mybatis实现crud项目改进https://gitee.com/he-yunlin/springboot/tree/springboot1.0/

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UDP服务器—实现数据通信

目录 前言 1.接口介绍 2.编写服务器 3.编写客户端 4.测试 总结 前言 在这篇文章中为大家介绍如何通过编码实现数据通信&#xff0c;实现思路是根据前面介绍的网络编程函数编写一个服务端和客户端&#xff0c;实现客户端和服务端双方通信 1.接口介绍 创建套接字 #include…

三平面映射的技术

大家好&#xff0c;我是阿赵。   之前在做护盾的时候&#xff0c;使用过一种叫做三平面映射的技术&#xff0c;这里来详细的说一下。 一、效果说明 在做场景的时候&#xff0c;很多美工都会遇到一个问题&#xff0c;想把一个通用的材质贴图赋予给一个经过拉伸的模型&#xf…

分布式应用:Zabbix自定义监控模板

目录 一、理论 1.zabbix监控模板 2.在客户端创建自定义 key 3.在 Web 页面创建自定义监控项模板 4.设置邮件报警 二、实验 1.在客户端创建自定义 key 2.在 Web 页面创建自定义监控项模板 3.设置邮件报警 三、问题 1.查看动作发送邮件失败 四、总结 一、理论 1.zab…

日常BUG——SpringBoot模糊映射

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 SpringBoot在启动时报出如下错误&#xff1a; Caused by: java.lang.IllegalStateExceptio…

【数学建模】--因子分析模型

因子分析有斯皮尔曼在1904年首次提出&#xff0c;其在某种程度上可以被看成时主成分分析的推广和扩展。 因子分析法通过研究变量间的相关稀疏矩阵&#xff0c;把这些变量间错综复杂的关系归结成少数几个综合因子&#xff0c;由于归结出的因子个数少于原始变量的个数&#xff0c…

Vim学习(二)—— 编译C程序

打开终端&#xff0c;这里以MobaXterm为例&#xff0c; 邮件创建新的空文件并命名&#xff0c; 然后cd到对应路径下&#xff0c;用 vim hello.cvim打开创建的文件&#xff0c;进入编辑模式&#xff0c;编辑完程序后按Esc退出编辑模式&#xff0c;输入 :wq保存并退出&#xf…

配置docker和复现

1.Nginx环境搭建 选择centos7来进行安装 1.1 创建Nginx的目录并进入 mkdir /soft && mkdir /soft/nginx/ cd /soft/nginx/ 1.2 下载Nginx的安装包&#xff0c;可以通过FTP工具上传离线环境包&#xff0c;或者通过wget命令在线获取安装包 wget https://nginx.org/down…

侯捷 C++ part2 兼谈对象模型笔记——5 三个C++11新特性

5 三个C11新特性 5.1 variadic templates 模板参数可变化&#xff0c;其语法为 ... (加在哪看情况) // 当参数pack里没有东西了就调用这个基本函数结束输出 void print() { }// 用于打印多个参数的可变参数模板函数 template <typename T, typename... Args> void pri…

系统架构设计高级技能 · 软件可靠性分析与设计(三)【系统架构设计师】

系列文章目录 系统架构设计高级技能 软件架构概念、架构风格、ABSD、架构复用、DSSA&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 系统质量属性与架构评估&#xff08;二&#xff09;【系统架构设计师】 系统架构设计高级技能 软件可靠性分析与设计…

[保研/考研机试] KY110 Prime Number 上海交通大学复试上机题 C++实现

题目链接&#xff1a; Prime Numberhttps://www.nowcoder.com/share/jump/437195121691717713466 描述 Output the k-th prime number. 输入描述&#xff1a; k≤10000 输出描述&#xff1a; The k-th prime number. 示例1 输入&#xff1a; 3 7 输出&#xff1a; …

EditPlus取消自动.bak备份

Tools->Preferences->File 将√取消

计算机视觉的应用10-图片中的表格结构识别与提取实战

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用10-图片中的表格结构识别与提取实战&#xff0c;表格结构识别在信息处理领域中具有广泛应用&#xff0c;但由于表格的多样性和复杂性&#xff0c;以及难以准确解析的布局和格式&#xff0c;传统的方…

PHP最简单自定义自己的框架实现像TP链式sql语句(六)

1、实现效果&#xff0c;链式sql语句封装 order、where、group、limit等封装 2、数据表构造函数入参&#xff0c;ModelBase.php public $table NULL; public function __construct($table){$this->table$table;if(!$this->table){die("no table" );}$this-&…

.NET对象的内存布局

在.NET中&#xff0c;理解对象的内存布局是非常重要的&#xff0c;这将帮助我们更好地理解.NET的运行机制和优化代码&#xff0c;本文将介绍.NET中的对象内存布局。 .NET中的数据类型主要分为两类&#xff0c;值类型和引用类型。值类型包括了基本类型(如int、bool、double、cha…

Gradio:交互式Python数据应用程序的新前沿

一、说明 什么是Gradio以及如何使用Gradio在Python中创建DataApp或Web界面&#xff1f;使用 Gradio 将您的 Python 数据科学项目转换为交互式应用程序。 摄影&#xff1a;Elijah Merrell on Unsplash Gradio是一个Python库&#xff0c;允许我们快速为机器学习模型创建可定制的接…

ChatGPT 的“自定义”功能对免费用户开放,在问题信息不足情况下还会反问来获取必要信息...

“ ChatGPT推出‘自定义’功能并向免费用户开放。即使信息有限&#xff0c;系统也能巧妙地通过反问获取必要细节&#xff0c;进一步提升了用户体验和互动效果。” 01 — 近期 ChatGPT 官方可能也发现绝大多数人用不好 Prompt 提示词&#xff0c;无法发挥彻底发挥大模型的优势&a…

wsl2安装docker引擎(Install Docker Engine on Debian)

安装 1.卸载旧版本 在安装 Docker 引擎之前&#xff0c;您必须首先确保卸载任何冲突的软件包。 发行版维护者在他们的存储库。必须先卸载这些软件包&#xff0c;然后才能安装 Docker 引擎的正式版本。 要卸载的非官方软件包是&#xff1a; docker.iodocker-composedocker-…

网神 SecGate 3600 防火墙任意文件上传漏洞复现(HW0day)

0x01 产品简介 网神SecGate3600下一代极速防火墙&#xff08;NSG系列&#xff09;是基于完全自主研发、经受市场检验的成熟稳定网神第三代SecOS操作系统 并且在专业防火墙、VPN、IPS的多年产品经验积累基础上精心研发的高性能下一代防火墙 专门为运营商、政府、军队、教育、大型…

【网络】高级IO

目录 一、五种IO模型 1、阻塞IO 2、非阻塞IO 3、信号驱动 4、IO多路转接 5、异步IO 6、总结 二、高级IO重要概念 1、同步通信与异步通信 2、阻塞 vs 非阻塞 三、非阻塞IO 1、fcntl 2、实现函数SetNoBlock 四、IO多路转接select 1、select 1.1、参数解释 1.2、…

Unity 编辑器资源导入处理函数 OnPostprocessAudio :深入解析与实用案例

Unity 编辑器资源导入处理函数 OnPostprocessAudio 用法 点击封面跳转下载页面 简介 在Unity中&#xff0c;我们可以使用编辑器资源导入处理函数&#xff08;OnPostprocessAudio&#xff09;来自定义处理音频资源的导入过程。这个函数是继承自AssetPostprocessor类的&#xff…