当前位置: 首页 > article >正文

【SpringBoot】黑马大事件笔记-day3

目录

文章管理部分

自定义注解校验

注解的概念

元注解

规定约束的注解

分页查询

OSS文件上传

获取AccessKey


上期回顾:

【SpringBoot】 黑马大事件笔记-day1

【SpringBoot】 黑马大事件笔记-day2

文章管理部分


自定义注解校验

先来看一下接口文档了解需求:

发布文章

基本信息

请求路径:/article
请求方式:POST
接口描述:该接口用于新增文章(发布文章)

请求参数

请求参数格式:application/json
请求参数说明:

请求数据样例:

{
"title": "陕西旅游攻略",
"content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
"coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b91ad-e0f4631cbed4.png",
"state": "草稿",
"categoryId": 2
}

         首先他是一个简单的增删查改的接口,但是我们需要进行参数的校验;其他的校验方法都提供了对应的注解,而 state 的校验没有提供注解。这里就需要我们自己去写满足规定的注解。

Controller

@RestController
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    @PostMapping
    public Result add(@RequestBody @Validated Article article) {
        articleService.add(article);
        return Result.success();
    }
}

Service

    @Override
    public void add(Article article) {
        // 更新创建时间与修改时间
        article.setCreateTime(LocalDateTime.now());
        article.setUpdateTime(LocalDateTime.now());
        // 获取用户信息 id
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer id = (Integer) map.get("id");
        article.setCreateUser(id);
        articleMapper.add(article);
    }

Mapper

    <insert id="add">
        INSERT INTO article(title, content, cover_img,state,category_id, create_user, create_time, update_time)
        VALUES (#{title},#{content},#{coverImg},#{state},#{categoryId},#{createUser},#{createTime},#{updateTime})
    </insert>

Pojo

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
    private Integer id;//主键ID
    @NotEmpty
    @Pattern(regexp = "^\\S{1,10}$")
    private String title;//文章标题
    @NotEmpty
    private String content;//文章内容
    @NotEmpty
    @URL
    private String coverImg;//封面图像
    @State
    private String state;//发布状态 已发布|草稿
    @NotNull
    private Integer categoryId;//文章分类id
    private Integer createUser;//创建人ID
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}

        其他注解根据接口文档提供的需求在字段加上对应的注解即可,@State 则需要我们自己来修改:

注解定义

interface

@Documented // 元注解
@Target(FIELD) // 元注解 ,FIELD 表示用在属性上
@Retention(RUNTIME) //元注解 ,表示运行时阶段生效
@Constraint(validatedBy = {StateValidated.class}) //填写校验规则类

public @interface State {
    // 提供校验失败的提示信息
    String message() default "State的值只能是已发布或者草稿";
    // 指定分组
    Class<?>[] groups() default {};
    // 负载
    // 获取到State注解的附加信息
    Class<? extends Payload>[] payload() default {};
}

注解的概念

元注解

        元注解是专门用来注解其他注解的注解,简单来说就是专门为自定义注解提供的注解。Java提供了五种元注解:

注解作用
@Documented注解是否将包含在JavaDoc中
@Retention什么时候使用该注解,用于描述注解的生命周期
@Target注解用于什么地方
@Inherited是否允许子类继承该注解
@Repeatable是否可重复注解

@Target 的注解运用范围:

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

@Retention 的注解定义的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

 以上的 ElementType、RetentionPolicy 是枚举。

规定约束的注解

        @Constraint 注解是 Validation 框架中的一个注解,用于自定义约束注解,即自定义校验规则。

        通过在自定义注解上添加 @Constraint 注解,可以将该注解标记为一个自定义约束注解。同时,需要指定一个实现了 Validator 接口的验证器类,用于验证该注解所标记的字段或参数是否符合自定义的校验规则。

Validator

// 泛型参数说明<给哪个注解提供校验规则,校验的数据类型>
public class StateValidated implements ConstraintValidator<State,String> {
    /*
    * value 表示校验数据
    * 方法体需要提供校验规则
    * 校验成功返回true,否则返回false
    */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if(value==null) return true;
        return value.equals("已发布") || value.equals("草稿");
    }
}

        这样我们的自定义注解 state 就已经完成了,简单总结一下:

创建一个 interface 注解 state
给 state 注解 添加元注解,声明运行范围与生命周期
添加  @Constraint 约束注解,并实现 Validator  约束类

        如果是非草稿或者已发布状态,那么程序就会抛出异常,这就说明我们的自定义注解的实现没有问题。

分页查询

先来看一下接口文档了解需求:

基本信息

请求路径:/article
请求方式:GET
接口描述:该接口用于根据条件查询文章

请求参数

请求参数格式:queryString

请求参数说明:

请求数据样例:

 pageNum=1&pageSize=3&categoryId=2&state=草稿

        分页查询是在 web 开发中常用的一种技术,当某个页面查询返回的数据量较大时,为了提高性能和用户体验不能将所有数据一次性返回给过前端,这时候就需要用到分页查询了。

        pagehelper 是一款开源的 Mybatis 第三方物理分页插件,依赖如下

    <pagehelper.version>1.4.6</pagehelper.version>
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>${pagehelper.version}</version>
    </dependency>

        我们以前都查询都是将所有结果直接给用户,而分页查询则是将全部查询信息按每页多少展现量进行反馈的;于是我们就需要一个类来记录分页的具体信息:

PageBean 类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean <T>{
    private Long total;   //总条数
    private List<T> items;//当前页数据集合
}

Controller

    @GetMapping
    public Result<PageBean<Article>> list(
            Integer pageNum,
            Integer pageSize,
            @RequestParam(required = false) Integer categoryId,
            @RequestParam(required = false) String state
    ) {
        PageBean<Article> pb =  articleService.list(pageNum,pageSize,categoryId,state);
        return Result.success(pb);
    }

因为展示的是文章详细,但是要按照分页的形式展示:

所以我们需要传的参数类型为 Result<PageBean<Article>> ,

@RequestParam(required = false) 表示被标注的参数不是必传项。

 Service

@Override
    public PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {
        // 创建 PageBean 对象
        PageBean<Article> pageBean = new PageBean<>();

        // 开启分页查询 PageHelper
        PageHelper.startPage(pageNum, pageSize);

        // 获取用户信息
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer userId = (Integer) map.get("id");

        // 调用mapper
        List<Article> as = articleMapper.list(userId,categoryId,state);

        // Page中提供了方法,可以获取PageHelper分页查询后
        // 得到的总记录条数和当前页数据
        Page<Article> p = (Page<Article>) as;

        // 把数据填充到 PageBean 对象并返回
        pageBean.setTotal(p.getTotal());
        pageBean.setItems(p.getResult());
        return pageBean;
    }
  • total:总记录数,表示满足查询条件的总记录数。
  • ltems:当前查询页数的集合类。

        使用的时候,只需在查询 list 前,调用 startPage 设置分页信息,即可使用分页功能。

Mapper

   <select id="list" resultType="com.thz.pojo.Article">
        SELECT * FROM article
        <where>
            create_user=#{userId}
            <if test="categoryId!=null">
                category_id=#{categoryId}
            </if>
            <if test="state!=null">
                and state=#{state}
            </if>
        </where>
    </select>

 

        以上是按一页两条文章作为规范的,当跳转到第二页时与数据库内容保持一致,则说明该文章分页的代码没有错误。

OSS文件上传


要是实现将文件上传到阿里云OSS,首先就要开通OSS服务:

        将鼠标移至产品,找到并单击对象存储OSS,打开OSS产品详情页面新用户可以免费试用三个月。

点这个创建Bucket:

        创建存储空间,给新建的Bucket命名,服务器可以默认选华北的,读写权限为公共读,点击完成创建就可以创建了

获取AccessKey

要获取AccessKey,点击头像,然后选择AccessKey管理。

 

点击创建即可 

创建完后会获得这两个键值对,需要保存备用 

 

阿里文档链接:JavaOSS文档

点击链接就可以看到官方对 OSS 提供的一些资料:

导入 OSS 依赖: 

    <aliyun.version>3.17.4</aliyun.version>
    <dependency>
      <groupId>com.aliyun.oss</groupId>
      <artifactId>aliyun-sdk-oss</artifactId>
      <version>${aliyun.version}</version>
    </dependency>

         阿里提供的文件上传源码可以在刚才的文档中找到,我们可以拿过来修改一下,作为我们项目的上传接口:

package com.thz.utils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;

import java.io.FileInputStream;
import java.io.InputStream;

public class AliOssUtil {

    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com";
    // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    //EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
    private static final String ACCESS_KEY_ID="换成你的AccessKey lD";
    private static final String ACCESS_KEY_SECRET="换成你的AccessKey Secret";
    // 填写Bucket名称,例如examplebucket。
    private static final String BUCKET_NAME = "换成你的Bucket名称";

    public static String uploadFile(String objectName, InputStream in) throws Exception {


        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID, ACCESS_KEY_SECRET);
        String url = "";
        try {
            // 填写字符串。
            String content = "Hello OSS,你好世界";

            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, objectName, in);

            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传字符串。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            //url组成: https://bucket名称.区域节点/objectName
            url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return url;
    }
}

注意:以上的 BUCKET_NAME、ACCESS_KEY_SECRET、ACCESS_KEY_SECRET 都需要设置成你自己OSS的配置。

最后完成 Controller 的编写即可:

    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file) throws Exception {
        // 把文件的内容存储到本地磁盘上
        String originalFilename = file.getOriginalFilename();
        // 保证文件的名字是唯一的,从而防止文件覆盖
        String filename = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));
        String url = AliOssUtil.uploadFile(filename,file.getInputStream());
        return Result.success(url);
    }

        在文件列表这里就可以查找自己的图片了,不过需要注意的是在OSS中我们是 url 的格式存储的,所以上传的文件名与 OSS 存储的文件名不一样也是应该的。


http://www.kler.cn/a/392092.html

相关文章:

  • CSS多列布局:打破传统布局的束缚
  • 论文阅读《BEVFormer v2》
  • 系统上线后发现bug,如何回退版本?已经产生的新业务数据怎么办?
  • LeetCode【0031】下一个排列
  • 使用Matlab建立随机森林
  • Oracle 11g rac 集群节点的修复过程
  • 用 Python 从零开始创建神经网络(二):第一个神经元的进阶
  • 停车共享小程序ssm+论文源码调试讲解
  • 实现linux定时备份数据至群晖NAS
  • python爬取newbing每日壁纸
  • JDBC事务管理、四大特征(ACID)、事务提交与回滚、MySQL事务管理
  • C语言串讲-2之指针和结构体
  • 2024 ECCV | DualDn: 通过可微ISP进行双域去噪
  • ubuntu20.04 解决Pycharm没有写入权限,无法通过检查更新更新的问题
  • k8s中基于overlay网络和underlay网络的网络插件分别有哪些
  • ima.copilot-腾讯智能工作台
  • react 中 FC 模块作用
  • int溢出值(c基础)
  • next中服务端组件共享接口数据
  • 基于yolov8、yolov5的番茄成熟度检测识别系统(含UI界面、训练好的模型、Python代码、数据集)
  • 2025年使用 AI 识别解决 reCAPTCHA
  • spring-IOC使用注解
  • Python的面向对象
  • SpringBoot+Vue,尽享个性化音乐推荐与分享的网站
  • 揭秘均值抽样分布:因果推断的统计学基础
  • 如何在 Spring Boot 中启用定时任务