[DDD] ValueObject的一种设计落地及应用

news2024/9/23 20:10:16

目录

  • 前言
  • 一、ValueObject
  • 二、设计
    • 2.1 接口
    • 2.2 单一值ValueObject
    • 2.3 单一字符串ValueObject
  • 三、实现
    • 3.1 示例
      • 3.1.1 PhoneNumber
      • 3.1.2 SocialCreditCode
  • 四、使用
    • 4.1 异常处理
    • 4.2 Json 反/序列化
      • 4.2.1 请求体
      • 4.2.2 HTTP接口
      • 4.2.3 用例
    • 4.3 JPA/MyBatis
      • 4.3.1 Converter或TypeHandler
      • 4.3.2 Entity
      • 4.3.3 Repository
      • 4.3.4 用例
    • 4.4 CACHE
      • 4.4.1 LocalBasedCache
      • 4.4.2 用例


前言

以前在InfoQ看到过这么一个讲座 Value-Objects-Dan-Bergh-Johnsson.

讲座的细节就不赘述了, 其中举例类似“电话号码”, “货币”在业务中的操作, 如果将这类有业务意义的字符串只是简单通过String/Integer等对象传递, 将丢失其业务意义, 最终编码, 测试都变得更繁琐. 同时程序员还需要在业务流程中时刻关心此类对象是否严格符合业务意义, 比如校验格式, 内容有效性等等. 实际工作看过来, 绝大多数人也都是这样做的.

如果使用ValueObject的设计思想, 设计一个包含“值”和其业务意义的对象, 例如“数量”一定非负之类的. 那么在实际使用中将使得校验, 编码, 测试, 甚至最基本的代码可读性都有明显提高.

本文介绍一种落地设计, 实现最常用的单一字符串值对象, 并参考Springboot环境, 实现接口自动化校验, DAO自动转换落库等等操作, 实现面向对象的编码.

Code Env: JDK21 + SpringBoot3+


一、ValueObject

值对象有两个主要特征:

  • 它们没有任何标识。
    • 没有唯一标识, 可以复用
  • 它们是不可变的。
    • Equals的比较是使用其“值”完成的

二、设计

本文仅对单一字符串值对象的设计作出说明, 因为此类值对象在实现接口, 或者落库时比较容易体会使用ValueObject的好处.

2.1 接口

仅分类, 因为不希望再手动调用校验, 这里就不设计校验的接口了

public interface ValueObject {}

定义单一值ValueObject

  • @JsonValue则提供了通过Jackson实现序列化的能力
    此时Jackson将直接序列化“值”而不是这个ValueObject对象
import com.fasterxml.jackson.annotation.JsonValue;

/**
 * @author hp
 */
public interface SingleValueObject<TYPE> extends ValueObject {

    @JsonValue
    TYPE value();
}

2.2 单一值ValueObject

实现ValueObject的基本特征

  • 值不可变, 在构造时需要提供值
  • equals, hashcode 通过其值完成, 而非对象本身.
  • @JsonAutoDetect 提供json序列化时获取非公共属性/方法的能力, 如果不提供公共getter, 则通过此注解获取值
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.hp.common.base.exception.NullValueObjectException;
import jakarta.annotation.PostConstruct;

import java.util.Objects;

/**
 * 配合jackson方便一些
 * <p>
 * 最好不要提供getter, 但是为了日志妥协一下
 *
 * @author hp
 * @see JsonAutoDetect;
 */
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC)
public abstract class AbstractSingleValueObject<TYPE> implements SingleValueObject<TYPE> {

    protected final TYPE value;

    @Override
    public TYPE value() {
        return value;
    }

    protected AbstractSingleValueObject(TYPE value) throws NullValueObjectException {
        if (Objects.isNull(value)) {
            throw new NullValueObjectException();
        }
        this.value = value;
    }

    protected abstract void validate(TYPE value) throws IllegalArgumentException;

    @Override
    public String toString() {
        return this.value.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        AbstractSingleValueObject<?> that = (AbstractSingleValueObject<?>) o;
        return Objects.equals(value, that.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}

2.3 单一字符串ValueObject

空字符串在此场景下理解为无意义的输入, 此时考虑通过直接在构造期间抛出异常的方式中断构造过程, 并返回NULL, 以保证没有合法输入就不构造出值对象的目的.

import cn.hutool.core.util.StrUtil;
import com.hp.common.base.exception.NullValueObjectException;

/**
 * @author hp
 */
public abstract class AbstractStringBasedSingleValueObject extends AbstractSingleValueObject<String> {
    protected AbstractStringBasedSingleValueObject(String value) throws NullValueObjectException {
        super(value);
        if (StrUtil.isEmpty(value)) {
            throw new NullValueObjectException();
        }
        validate(value);
    }
}

三、实现

需要说明的是, 实现类不一定完全实现了此类值在现实生活中包含的所有方面, 可以根据业务场景做简单调整和取舍. 比如下文的电话号码示例就省略了区号的信息.

3.1 示例

  • 私有化构造, 仅通过静态方法创建对象
    • @JsonCreator提供了Jackson在反序列化时指定创建对象方法的入口, 这里指定使用静态方法
  • 当输入NULL或空字符串时, 业务上视为无意义的输入, 将不做实例化
  • 当输入非“空”字符串时, 在构造时将根据子类实现的规则进行校验, 并在校验失败时抛出IllegalArgumentException供捕获

3.1.1 PhoneNumber

import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.base.Preconditions;
import com.hp.common.base.exception.NullValueObjectException;
import com.hp.common.base.valueobject.AbstractStringBasedSingleValueObject;
import com.hp.common.base.valueobject.Patterns;

import java.util.Optional;

/**
 * @author hp
 */
public final class PhoneNumber extends AbstractStringBasedSingleValueObject {

    private PhoneNumber(String phoneNumber) throws NullValueObjectException {
        super(phoneNumber);
    }

    @JsonCreator
    public static PhoneNumber of(String value) {
        try {
            return new PhoneNumber(value);
        } catch (NullValueObjectException ignore) {
            return null;
        }
    }

    @JsonCreator
    public static PhoneNumber of(Long value) {
        return Optional.ofNullable(value)
                .map(String::valueOf)
                .map(PhoneNumber::of)
                .orElse(null);
    }

    @Override
    public void validate(String value) throws IllegalArgumentException {
        Preconditions.checkArgument(Patterns.PHONE_PATTERN.asPredicate().test(value), "手机号码格式错误");
    }
}

3.1.2 SocialCreditCode

import com.fasterxml.jackson.annotation.JsonCreator;
import com.google.common.base.Preconditions;
import com.hp.common.base.exception.NullValueObjectException;
import com.hp.common.base.valueobject.AbstractStringBasedSingleValueObject;
import com.hp.common.base.valueobject.Patterns;

/**
 * @author hp
 */
public final class SocialCreditCode extends AbstractStringBasedSingleValueObject {

    private SocialCreditCode(String value) throws NullValueObjectException {
        super(value);
    }

    @JsonCreator
    public static SocialCreditCode of(String value){
        try {
            return new SocialCreditCode(value);
        }catch (NullValueObjectException ignore){
            return null;
        }
    }

    @Override
    public void validate(String value) throws IllegalArgumentException {
        Preconditions.checkArgument(Patterns.CREDIT_CODE_PATTERN.asPredicate().test(value), "统一社会信用代码格式错误");
    }
}

四、使用

4.1 异常处理

可以根据公司情况, 自定义参数校验失败的自定义异常. 这里用最简单的IllegalArgumentException作示例

package com.hp.valueobject.exception;

import com.hp.common.base.model.Returns;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author hp
 */
@Slf4j
@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(IllegalArgumentException.class)
    public Returns<?> handleIllegalArgumentsException(IllegalArgumentException e) {
        log.error("请求参数错误", e);
        return Returns.fail().message(e.getMessage());
    }
}

4.2 Json 反/序列化

最常见场景之一, RESTful接口参数的Json序列化场景

4.2.1 请求体

package com.hp.valueobject.request;

import com.hp.common.base.model.Request;
import com.hp.common.base.valueobject.contact.PhoneNumber;
import com.hp.common.base.valueobject.socialcreditcode.SocialCreditCode;
import lombok.Data;

/**
 * @author hp
 */
@Data
public class ValueObjectPostRequest implements Request {

    private PhoneNumber phone;
 
    private SocialCreditCode socialCreditCode;
    
}

4.2.2 HTTP接口

package com.hp.valueobject.controller;

import com.hp.common.base.model.Returns;
import com.hp.common.base.valueobject.contact.PhoneNumber;
import com.hp.valueobject.request.ValueObjectPostRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author hp
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("valueobject")
public class ValueObjectController {

    @PostMapping("postRequest")
    public Returns<?> postRequest(@RequestBody ValueObjectPostRequest request) {
        return Returns.success().data(request);
    }

    @GetMapping("getRequest")
    public Returns<?> getRequest(@RequestParam PhoneNumber phone) {
        return Returns.success().data(phone);
    }
}

4.2.3 用例

用例格式为Idea http client.

POST Request, phone正确, 信用代码空字符串无意义

# Request
POST http://localhost:9988/valueobject/postRequest
Content-Type: application/json
Content-Length: 54
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.9)
Accept-Encoding: br,deflate,gzip,x-gzip

{
  "phone": "18123123123",
  "socialCreditCode": ""
} 

# Response
POST http://localhost:9988/valueobject/postRequest

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 22 Mar 2024 06:17:13 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "操作成功",
  "data": {
    "phone": "18123123123",
    "socialCreditCode": null
  }
}
Response code: 200; Time: 37ms (37 ms); Content length: 219 bytes (219 B)

POST phone 参数错误 10 位

POST http://localhost:9988/valueobject/postRequest
Content-Type: application/json
Content-Length: 27
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.9)
Accept-Encoding: br,deflate,gzip,x-gzip

{
  "phone": "1812323123"
}
###

POST http://localhost:9988/valueobject/postRequest

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 22 Mar 2024 06:12:38 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 500,
  "message": "手机号码格式错误",
  "data": null
}

Response code: 200; Time: 118ms (118 ms); Content length: 45 bytes (45 B)

GET phone格式正确

GET http://localhost:9988/valueobject/getRequest?phone=18123123123

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 22 Mar 2024 06:21:27 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "操作成功",
  "data": "18123123123"
}
Response file saved.
> 2024-03-22T142127.200.json

Response code: 200; Time: 10ms (10 ms); Content length: 50 bytes (50 B)

GET phone格式错误

GET http://localhost:9988/valueobject/getRequest?phone=1812312313

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 22 Mar 2024 06:22:23 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 500,
  "message": "手机号码格式错误",
  "data": null
}
Response file saved.
> 2024-03-22T142223.200.json

Response code: 200; Time: 25ms (25 ms); Content length: 45 bytes (45 B)

4.3 JPA/MyBatis

4.3.1 Converter或TypeHandler

PhoneNumber示例

JPA converter

package com.hp.jpa.convertor;

import com.hp.common.base.valueobject.AbstractSingleValueObject;
import com.hp.common.base.valueobject.AbstractStringBasedSingleValueObject;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.Optional;

@Converter
public abstract class AbstractStringBasedSingleValueObjectConverter<T extends AbstractStringBasedSingleValueObject> implements AttributeConverter<T, String> {
    public AbstractStringBasedSingleValueObjectConverter() {
    }

    public String convertToDatabaseColumn(T attribute) {
        return (String)Optional.ofNullable(attribute).map(AbstractSingleValueObject::value).orElse("");
    }
}

package com.hp.valueobject.converter;

import com.hp.common.base.valueobject.contact.PhoneNumber;
import com.hp.jpa.convertor.AbstractStringBasedSingleValueObjectConverter;
import jakarta.persistence.Converter;

/**
 * @author hp
 */
@Converter
public class PhoneNumberJPAConverter extends AbstractStringBasedSingleValueObjectConverter<PhoneNumber> {
    @Override
    public PhoneNumber convertToEntityAttribute(String dbData) {
        return PhoneNumber.of(dbData);
    }
}

Mybatis-plus typeHandler

package com.hp.mybatisplus.convertor;

import com.hp.common.base.valueobject.AbstractStringBasedSingleValueObject;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;

public abstract class AbstractStringBasedSingleValueObjectConverter<T extends AbstractStringBasedSingleValueObject> implements TypeHandlerCodeGenAdapter<T, String> {
    public AbstractStringBasedSingleValueObjectConverter() {
    }

    public void setParameter(PreparedStatement ps, int i, T t, JdbcType jdbcType) throws SQLException {
        ps.setString(i, (String)t.value());
    }
}
package com.hp.valueobject.converter;

import com.hp.common.base.valueobject.contact.PhoneNumber;
import com.hp.mybatisplus.convertor.AbstractStringBasedSingleValueObjectConverter;

import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author hp
 */
public class PhoneNumberMybatisTypeHandler extends AbstractStringBasedSingleValueObjectConverter<PhoneNumber> {
    @Override
    public PhoneNumber getResult(ResultSet rs, String columnName) throws SQLException {
        return PhoneNumber.of(rs.getString(columnName));
    }

    @Override
    public PhoneNumber getResult(ResultSet rs, int columnIndex) throws SQLException {
        return PhoneNumber.of(rs.getString(columnIndex));
    }

    @Override
    public PhoneNumber getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return PhoneNumber.of(cs.getString(columnIndex));
    }
}

4.3.2 Entity

@Entity
@Table(name = "unified_social_credit_code")
@Getter
@Setter
public class UnifiedSocialCreditCode {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Convert(converter = PhoneNumberConverter.class)
    private PhoneNumber username;

    @Convert(converter = SocialCreditCodeConverter.class)
    private SocialCreditCode socialCreditCode;

4.3.3 Repository

直接传递ValueObject类型参数即可, QueryDSL也可以正常使用
注: MyBatis省略, 其低版本无法在自定义查询中自动通过typeHandler提取值, 需要手动 ValueObject.value();

@Repository
public interface JpaBasedUnifiedSocialCreditCodeDao extends BaseRepository<UnifiedSocialCreditCode, Long> {
    List<UnifiedSocialCreditCode> findAllBySocialCreditCodeIn(Collection<SocialCreditCode> codes);
}

4.3.4 用例

JPA

@Test
public void givenUSCC_whenQueryInDB_thenReturnsNonnull() {
    // given
    final String unifiedSocialCreditCode = "91510115MABRCTYM2W";
    final SocialCreditCode socialCreditCode = SocialCreditCode.of(unifiedSocialCreditCode);

    // when
    final List<UnifiedSocialCreditCode> list = unifiedSocialCreditCodeRepository.findAllBySocialCreditCode(Lists.newArrayList(socialCreditCode));

    // then
    assertThat(list).isNotEmpty().size().isGreaterThanOrEqualTo(1);
    final UnifiedSocialCreditCode first = list.getFirst();
    assertThat(first.getSocialCreditCode()).isEqualTo(socialCreditCode);
    assertThat(first.getUsername()).isNotNull();
}

4.4 CACHE

缓存场景, 这里主要是针对服务内缓存的说明, 例如使用Redis等中间件时, 都需要序列化, 此时使用jackson序列化即可

4.4.1 LocalBasedCache

例如使用Map作为容器的场景, 因为在AbstractSingleValueObject中已经重写了hashCode和equals, 使得ValueObject可以直接作为键完成存储和比较

@Slf4j
@Component
public class LocalBasedCache implements USCCCache {

    private final static Map<SocialCreditCode, List<UserCacheModel>> CACHE = Maps.newConcurrentMap();

    @Override
    public boolean exist(SocialCreditCode socialCreditCode) {
        return CACHE.containsKey(socialCreditCode);
    }

    @Override
    public void put(SocialCreditCode socialCreditCode, UserCacheModel model) {
        CACHE.compute(socialCreditCode, (key, value) -> {
            if (Objects.isNull(value)) {
                return Lists.newArrayList(model);
            } else {
                value.add(model);
                return value;
            }
        });
    }

    @Override
    public List<UserCacheModel> get(SocialCreditCode socialCreditCode) {
        return CACHE.getOrDefault(socialCreditCode, Collections.emptyList());
    }

    @Override
    public void remove(SocialCreditCode socialCreditCode) {
        CACHE.remove(socialCreditCode);
    }
}

4.4.2 用例

 @Test
 public void givenSocialCreditCode_whenCallPutAndExist_thenSuccess() {
     // given
     final LocalBasedCache cache = new LocalBasedCache();
     final SocialCreditCode socialCreditCode = SocialCreditCode.of("915101007130091284");
     final SocialCreditCode socialCreditCode2 = SocialCreditCode.of("915101007130091284");
     final SocialCreditCode socialCreditCode3 = SocialCreditCode.of("915101007130091283");

     // when
     cache.put(socialCreditCode, new UserCacheModel(1L,"1"));

     // then
     assertThat(cache.exist(socialCreditCode)).isTrue();
     assertThat(cache.exist(socialCreditCode2)).isTrue();
     assertThat(cache.exist(socialCreditCode3)).isFalse();
 }

测试结果
在这里插入图片描述

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

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

相关文章

HarmonyOS实战开发-如何使用首选项能力实现一个简单示例。

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 首选…

第二证券|基本面向好预期强化 全球资本加紧布局A股

开年以来&#xff0c;在我国经济上升向好的态势持续稳固增强的大布景下&#xff0c;结合各方努力&#xff0c;A股商场企稳上升痕迹明显。受一系列稳定商场预期政策出台的加持&#xff0c;全球本钱正在加速布局A股商场。 业界人士指出&#xff0c;当时我国本钱商场依然具有明显…

QT(6.5) cmake构建C++编程,调用python (已更新:2024.3.23晚)

一、注意事项 explicit c中&#xff0c;一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)&#xff0c;承担了两个角色&#xff0c;构造器、类型转换操作符&#xff0c; c提供关键字explicit&#xff0c;阻止转换构造函数进行的隐式转换的发生&#…

jvm底层

逐步细化 静态链接&#xff1a;静态方法(符号引用)替换为内存指针或者句柄直接引用) 动态链接&#xff1a;程序期间将符号引用替换为直接引用 对象头&#xff1a; 指针压缩&#xff1a; -XX:UseCompressedOops 开启指针压缩 减少内存消耗&#xff1b;大指针在主内存 缓存间移…

人脸聚类原理和算法解释

人脸聚类是指将大量人脸图像根据它们的相似性分组到不同的群集中的过程。人脸聚类通常利用人脸的特征向量表示来度量人脸之间的相似性&#xff0c;并将相似的人脸图像聚集在一起。 以下是人脸聚类的一般原理&#xff1a; 人脸特征提取&#xff1a;对每张人脸图像提取特征向量。…

上海市开展专项行动,提升车联网行业网络和数据安全防护水平

近日&#xff0c;上海市通信管理局发布了《关于开展“铸盾车联”2024年车联网网络和数据安全专项行动的通知》。通知中提到&#xff0c;此次专项行动是为了提升本市车联网行业网络和数据安全防护水平&#xff0c;筑牢车联网网络和数据安全防线&#xff0c;护航智能网联汽车产业…

Spring之事务原理篇

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;Github传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有…

opencv各个模块介绍(1)

Core 模块&#xff1a;核心模块&#xff0c;提供了基本的数据结构和功能。 常用的核心函数&#xff1a; cv::Mat&#xff1a;表示多维数组的数据结构&#xff0c;是OpenCV中最常用的类之一&#xff0c;用于存储图像数据和进行矩阵运算。 cv::Scalar&#xff1a;用于表示多通道…

Redis - 高并发场景下的Redis最佳实践_翻过6座大山

文章目录 概述6座大山之_缓存雪崩 &#xff08;缓存全部失效&#xff09;缓存雪崩的两种常见场景如何应对缓存雪崩&#xff1f; 6座大山之_缓存穿透&#xff08;查询不存在的 key&#xff09;缓存穿透的原因解决方案1. 数据校验2. 缓存空值3. 频控4. 使用布隆过滤器 6座大山之_…

水果检测15种YOLOV8

水果检测15种YOLOV8&#xff0c;只需要OPENCV&#xff0c;采用YOLOV8训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV调用&#xff0c;支持C/PYTHON/ANDROID开发

41 arr.at is not a function

前言 一台机器 获取前端服务1, 一个列表能够展示出来 然后 一台机器 同样获取前端服务1, 这个列表展示不出来 然后 console里面没有任何报错[实际上是有报错, 但是没看到, 需要在vue的js代码里面去调试] 然后 这里面最终出现问题的地方是 Array.at 的使用, 我这边 js引擎版…

Spring Security安全管理

目录 一.添加依赖 效果 二.设置配置文件 认证 1.密码生成器 BCryptPasswordEncoder 配置文件中 2.inMemoryAuthentication内存认证方法 授权 效果 登录 效果 三.UserDetailsService认证授权方式 新建数据库 实体类 Role User 接口 实现类 配置文件 效果 四…

(AtCoder Beginner Contest 325) ---- D - Printing Machine -- 题解

目录 D - Printing Machine&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 代码实现&#xff1a; D - Printing Machine&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 打印一次后&#xff0c;需要充电一微秒后才能再次打印就可以看作每微妙只能打印一…

Vue3更新Package.json版本号

由于我之前已经更新过了&#xff0c;下面的方法提示我已经是最新的了&#xff0c;记录一下&#xff0c;过段时间在测试一下 npm install -g vue/clivue upgrade

Gitee删除自己本地仓库

1、打开自己的本地仓库 2、点击管理 3、选择删除仓库 4、将□的内容复制到⭕里

文件上传一-WEB攻防-PHP应用文件上传函数缺陷条件竞争二次渲染黑白名单JS绕过9

演示案例&#xff1a; PHP-原生态-文件上传-前后端验证PHP-原生态-文件上传-类型文件头验证PHP-原生态-文件上传-后缀黑白名单验证PHP-原生态-文件上传-解析配置&二次渲染PHP-原生态-文件上传-逻辑缺陷&函数缺陷 #学习前必读&#xff1a; 1、课前一定要明白&#xff1a…

nginx: [emerg] stream directive is duplicate in /etc/nginx/nginx.conf:56

背景&#xff1a; 在维护paas平台的时候发现一个web前端容器服务运行报错&#xff0c;提示如下&#xff1a; 问题分析&#xff1a; 根据日志的内容&#xff0c;发现是nginx.conf配置文件的stream模块配置存在问题导致的。需要查看一下nginx.conf配置文件的内容&#xff1a; 注…

LeetCode Python - 73. 矩阵置零

目录 题目描述解法方法一&#xff1a;数组标记方法二&#xff1a;原地标记 运行结果方法一方法二 题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;…

FFmpeg拉取RTSP流并定时生成10秒短视频

生成效果: 视频时长为10秒 生成格式为FLV 输出日志: 完整实现代码如下: 需要在Mac和终端先安装FFmpeg brew install ffmpeg CMake文件配置: cmake_minimum_required(VERSION 3.27) project(ffmpeg_open_stream) set(CMAKE_CXX_STANDARD 17)#头文件包目录 include_director…

可调恒流电子负载优点和应用

可调恒流电子负载是一种可以模拟真实负载的电子设备&#xff0c;它可以在电源电压和电流范围内提供恒定的电流或电压。这种设备在许多领域都有广泛的应用&#xff0c;如电力系统、通信设备、汽车电子、航空航天等。以下是可调恒流电子负载的优点和应用。 优点&#xff1a; 精确…