超全分析MybatisPlus中的MetaObjectHandler全局字段填充的基本知识(附Demo及实战)

目录

  • 前言
  • 1. 源码及API
  • 2. Demo架构
  • 3. 全局字段填充(实战)
  • 4. 局部字段不填充(实战)

前言

对于Java的相关知识推荐阅读:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)

MetaObjectHandler 是 MyBatis-Plus 提供的一个接口,用于处理在插入更新操作时的一些公共字段的自动填充

在这里插入图片描述

比如,在插入记录时自动填充 createTime 字段,在更新记录时自动填充 updateTime 字段
(一开始实战过程中莫名其妙的填充了这两个数据值,但是查找数据库触发器确没有这个,好奇哪里触发的,给我上了一课,对此详细研究并且科普该知识点)

1. 源码及API

通过源码分析各个方法:

/*
 * Copyright (c) 2011-2023, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.core.handlers;

import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.reflection.MetaObject;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * 元对象字段填充控制器抽象类,实现公共字段自动写入<p>
 * <p>
 * 所有入参的 MetaObject 必定是 entity 或其子类的 MetaObject
 *
 * @author hubin
 * @since 2016-08-28
 */
public interface MetaObjectHandler {

    /**
     * 是否开启了插入填充
     */
    default boolean openInsertFill() {
        return true;
    }

    /**
     * 是否开启了更新填充
     */
    default boolean openUpdateFill() {
        return true;
    }

    /**
     * 插入元对象字段填充(用于插入时对公共字段的填充)
     *
     * @param metaObject 元对象
     */
    void insertFill(MetaObject metaObject);

    /**
     * 更新元对象字段填充(用于更新时对公共字段的填充)
     *
     * @param metaObject 元对象
     */
    void updateFill(MetaObject metaObject);

    /**
     * 通用填充
     *
     * @param fieldName  java bean property name
     * @param fieldVal   java bean property value
     * @param metaObject meta object parameter
     */
    default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
        if (Objects.nonNull(fieldVal) && metaObject.hasSetter(fieldName)) {
            metaObject.setValue(fieldName, fieldVal);
        }
        return this;
    }

    /**
     * get value from java bean by propertyName
     *
     * @param fieldName  java bean property name
     * @param metaObject parameter wrapper
     * @return 字段值
     */
    default Object getFieldValByName(String fieldName, MetaObject metaObject) {
        return metaObject.hasGetter(fieldName) ? metaObject.getValue(fieldName) : null;
    }

    /**
     * find the tableInfo cache by metaObject </p>
     * 获取 TableInfo 缓存
     *
     * @param metaObject meta object parameter
     * @return TableInfo
     * @since 3.3.0
     */
    default TableInfo findTableInfo(MetaObject metaObject) {
        return TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
        return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
        return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        return strictFill(true, tableInfo, metaObject, strictFills);
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
        return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
        return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
    }

    /**
     * @param metaObject metaObject meta object parameter
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictUpdateFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        return strictFill(false, tableInfo, metaObject, strictFills);
    }

    /**
     * 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(null 值不填充)
     *
     * @param insertFill  是否验证在 insert 时填充
     * @param tableInfo   cache 缓存
     * @param metaObject  metaObject meta object parameter
     * @param strictFills 填充信息
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
        if ((insertFill && tableInfo.isWithInsertFill()) || (!insertFill && tableInfo.isWithUpdateFill())) {
            strictFills.forEach(i -> {
                final String fieldName = i.getFieldName();
                final Class<?> fieldType = i.getFieldType();
                tableInfo.getFieldList().stream()
                    .filter(j -> j.getProperty().equals(fieldName) && fieldType.equals(j.getPropertyType()) &&
                        ((insertFill && j.isWithInsertFill()) || (!insertFill && j.isWithUpdateFill()))).findFirst()
                    .ifPresent(j -> strictFillStrategy(metaObject, fieldName, i.getFieldVal()));
            });
        }
        return this;
    }

    /**
     * 填充策略,默认有值不覆盖,如果提供的值为null也不填充
     *
     * @param metaObject metaObject meta object parameter
     * @param fieldName  java bean property name
     * @param fieldVal   java bean property value of Supplier
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) {
        if (getFieldValByName(fieldName, metaObject) == null) {
            setFieldValByName(fieldName, fieldVal, metaObject);
        }
        return this;
    }

    /**
     * 严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充
     *
     * @param metaObject metaObject meta object parameter
     * @param fieldName  java bean property name
     * @param fieldVal   java bean property value of Supplier
     * @return this
     * @since 3.3.0
     */
    default MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
        if (metaObject.getValue(fieldName) == null) {
            Object obj = fieldVal.get();
            if (Objects.nonNull(obj)) {
                metaObject.setValue(fieldName, obj);
            }
        }
        return this;
    }
}

对应的代码都较为简单,此处以API的概念属性以及如何调用进行讲解

方法名概念作用参数调用方式
openInsertFill()检查是否开启插入填充返回布尔值,表示是否在插入时进行字段填充boolean open = metaObjectHandler.openInsertFill();
openUpdateFill()检查是否开启更新填充返回布尔值,表示是否在更新时进行字段填充boolean open = metaObjectHandler.openUpdateFill();
insertFill(MetaObject metaObject)插入操作时填充元对象字段定义插入时的字段自动填充逻辑MetaObject metaObject: 元对象metaObjectHandler.insertFill(metaObject);
updateFill(MetaObject metaObject)更新操作时填充元对象字段定义更新时的字段自动填充逻辑MetaObject metaObject: 元对象metaObjectHandler.updateFill(metaObject);
setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)通用字段填充方法根据字段名设置字段值String fieldName: 字段名
Object fieldVal: 字段值
MetaObject metaObject: 元对象
metaObjectHandler.setFieldValByName(“fieldName”, fieldVal, metaObject);
getFieldValByName(String fieldName, MetaObject metaObject)获取字段值根据字段名获取字段值String fieldName: 字段名
MetaObject metaObject: 元对象
Object value = metaObjectHandler.getFieldValByName(“fieldName”, metaObject);
findTableInfo(MetaObject metaObject)获取表信息根据元对象查找表信息缓存MetaObject metaObject: 元对象TableInfo tableInfo = metaObjectHandler.findTableInfo(metaObject);
strictInsertFill(MetaObject metaObject, String fieldName, Class fieldType, E fieldVal)严格模式插入填充在插入操作时严格填充指定字段MetaObject metaObject: 元对象
String fieldName: 字段名
Class fieldType: 字段类型
E fieldVal: 字段值
metaObjectHandler.strictInsertFill(metaObject, “fieldName”, fieldType, fieldVal);
strictInsertFill(MetaObject metaObject, String fieldName, Supplier fieldVal, Class fieldType)严格模式插入填充在插入操作时严格填充指定字段MetaObject metaObject: 元对象
String fieldName: 字段名
Supplier fieldVal: 字段值提供者
Class fieldType: 字段类型
metaObjectHandler.strictInsertFill(metaObject, “fieldName”, fieldVal, fieldType);
strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills)严格模式插入填充在插入操作时严格填充指定字段TableInfo tableInfo: 表信息
MetaObject metaObject: 元对象
List<StrictFill<?, ?>> strictFills: 填充信息列表
metaObjectHandler.strictInsertFill(tableInfo, metaObject, strictFills);
strictUpdateFill(MetaObject metaObject, String fieldName, Supplier fieldVal, Class fieldType)严格模式更新填充在更新操作时严格填充指定字段MetaObject metaObject: 元对象
String fieldName: 字段名
Supplier fieldVal: 字段值提供者
Class fieldType: 字段类型
metaObjectHandler.strictUpdateFill(metaObject, “fieldName”, fieldVal, fieldType);
strictUpdateFill(MetaObject metaObject, String fieldName, Class fieldType, E fieldVal)严格模式更新填充在更新操作时严格填充指定字段MetaObject metaObject: 元对象
String fieldName: 字段名
Class fieldType: 字段类型
E fieldVal: 字段值
metaObjectHandler.strictUpdateFill(metaObject, “fieldName”, fieldType, fieldVal);
strictUpdateFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills)严格模式更新填充在更新操作时严格填充指定字段TableInfo tableInfo: 表信息
MetaObject metaObject: 元对象
List<StrictFill<?, ?>> strictFills: 填充信息列表
metaObjectHandler.strictUpdateFill(tableInfo, metaObject, strictFills);
strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills)严格填充策略根据表信息和元对象进行严格的插入或更新填充boolean insertFill: 插入/更新标志
TableInfo tableInfo: 表信息
MetaObject metaObject: 元对象
List<StrictFill<?, ?>> strictFills: 填充信息列表
metaObjectHandler.strictFill(insertFill, tableInfo, metaObject, strictFills);
fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal)填充策略根据策略填充字段值,默认是字段值为null时才填充MetaObject metaObject: 元对象
String fieldName: 字段名
Object fieldVal: 字段值
metaObjectHandler.fillStrategy(metaObject, “fieldName”, fieldVal);
strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal)严格模式填充策略严格填充字段值,只有在当前字段值为null时才填充MetaObject metaObject: 元对象
String fieldName: 字段名
Supplier<?> fieldVal: 字段值提供者
metaObjectHandler.strictFillStrategy(metaObject, “fieldName”, fieldVal);

方法包括检查是否开启填充、具体的填充实现、字段值的设置和获取策略

2. Demo架构

具体的示例如下:

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        System.out.println("start insert fill ....");
        // 使用 strictInsertFill 方法插入时间字段
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        System.out.println("start update fill ....");
        // 使用 strictUpdateFill 方法更新时间字段
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

常用的填充方法如下:

  • strictInsertFill: 严格模式下的插入填充,只有在字段为 null 时才会填充,避免覆盖已有值
  • strictUpdateFill: 严格模式下的更新填充,只有在字段为 null 时才会填充,避免覆盖已有值
  • fillStrategy::指定填充策略,可选值包括 NOT_NULL(仅当字段为 null 时填充)和 DEFAULT(无条件填充)

对应的实体类如下:

@TableName("user")
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    // getters and setters
}

上述注解的说明如下(源码说明):

public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}

对应还需要增加一个配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MyMetaObjectHandler();
    }
}

最终执行插入操作并更新:

User user = new User();
userMapper.insert(user);
// user.getCreateTime() 会被自动填充
userMapper.updateById(user);

此种情况下自动填充属性会失效

  • 字段未使用 @TableField 注解:未使用 @TableField 注解或 fill 属性未设置为 FieldFill.INSERTFieldFill.UPDATE,自动注入将不会生效

  • 未实现 MetaObjectHandler 接口:MyBatis-Plus 将不知道如何处理自动填充逻辑。

  • 未在配置类中注册 MetaObjectHandler:即使实现了 MetaObjectHandler 接口,如果未在 Spring 配置类中进行注册,自动填充也不会生效

3. 全局字段填充(实战)

一些开源项目经常会让我们在创建表的过程中需要保留一些字段,究其原因是属性的自动填充

对于项目中

public class DefaultDBFieldHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
    
        if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
            BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();

            LocalDateTime current = LocalDateTime.now();
            // 创建时间为空,则以当前时间为插入时间
            if (Objects.isNull(baseDO.getCreateTime())) {
                baseDO.setCreateTime(current);
            }
            // 更新时间为空,则以当前时间为更新时间
            if (Objects.isNull(baseDO.getUpdateTime())) {
                baseDO.setUpdateTime(current);
            }
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时间为空,则以当前时间为更新时间
        Object modifyTime = getFieldValByName("updateTime", metaObject);
        if (Objects.isNull(modifyTime)) {
            setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        }
    }
}

其中BaseDO是我的一些固有属性类:

@Data
@JsonIgnoreProperties(value = "transMap") // 避免 Jackson 在 Spring Cache 反序列化报错
public abstract class BaseDO implements Serializable, TransPojo {

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * 最后更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * 创建者,目前使用 SysUser 的 id 编号
     *
     * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
     */
    @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String creator;
    /**
     * 更新者,目前使用 SysUser 的 id 编号
     *
     * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
     */
    @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
    private String updater;
    /**
     * 是否删除
     */
    @TableLogic
    private Boolean deleted;

}

同步增加其配置类:

public class MybatisAutoConfiguration {

    @Bean
    public MetaObjectHandler defaultMetaObjectHandler(){
        return new DefaultDBFieldHandler(); // 自动填充参数类
    }
}

对应正式的功能如下:

@Service
@Validated
public class GoodsStoragePlanServiceImpl implements GoodsStoragePlanService {

    @Resource
    private GoodsStoragePlanMapper goodsStoragePlanMapper;

    @Resource
    private AdminUserService userService;


    @Override
    public Long createGoodsStoragePlan(GoodsStoragePlanSaveReqVO createReqVO) {
        // 插入
        GoodsStoragePlanDO goodsStoragePlan = BeanUtils.toBean(createReqVO, GoodsStoragePlanDO.class);
        goodsStoragePlanMapper.insert(goodsStoragePlan);
    }
}

后续便会自动填充相应的属性

4. 局部字段不填充(实战)

代码和第三章的实战差不多,在上面代码的改进

需求:

上面的创建是增加时间和修改时间都会被自动注入
由于我这个只需要增加时间自动注入,修改时间只有在创建的时候自动注入,创建的时候需要为空

增加临时禁用全局字段的填充机制:引入一个上下文变量来控制是否启用字段填充

  1. 创建一个 ThreadLocal 变量来控制是否启用字段填充:
    ThreadLocal 变量为每个线程提供了独立的变量副本,从而保证了线程安全
    每个线程可以独立地访问其 ThreadLocal 变量副本,而不会影响其他线程
/**
 * FieldFillContext 类用于控制全局字段填充机制。
 * 使用 ThreadLocal 变量来保证线程安全,实现线程独立的变量副本。
 */
public class FieldFillContext {

    /**
     * ThreadLocal 变量,用于存储每个线程是否启用字段填充的状态。
     * 初始值为 true,即默认启用字段填充。
     */
    private static final ThreadLocal<Boolean> ENABLE_FIELD_FILL = ThreadLocal.withInitial(() -> true);

    /**
     * 设置当前线程是否启用字段填充。
     *
     * @param enable 如果为 true,则启用字段填充;如果为 false,则禁用字段填充。
     */
    public static void setEnableFieldFill(boolean enable) {
        ENABLE_FIELD_FILL.set(enable);
    }

    /**
     * 获取当前线程是否启用字段填充的状态。
     *
     * @return 如果启用字段填充,则返回 true;否则返回 false。
     */
    public static boolean isEnableFieldFill() {
        return ENABLE_FIELD_FILL.get();
    }

    /**
     * 清除当前线程的 ThreadLocal 变量,防止内存泄漏。
     */
    public static void clear() {
        ENABLE_FIELD_FILL.remove();
    }
}
  1. 修改 MetaObjectHandler 使用上下文变量
    在 MetaObjectHandler 中检查上下文变量的值,决定是否进行字段填充:
public class DefaultDBFieldHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        if (!FieldFillContext.isEnableFieldFill()) {
            return;
        }
        
        /**
        * 以下没变动
        */ 
        if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
            BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();

            LocalDateTime current = LocalDateTime.now();
            // 创建时间为空,则以当前时间为插入时间
            if (Objects.isNull(baseDO.getCreateTime())) {
                baseDO.setCreateTime(current);
            }
            // 更新时间为空,则以当前时间为更新时间
            if (Objects.isNull(baseDO.getUpdateTime())) {
                baseDO.setUpdateTime(current);
            }
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时间为空,则以当前时间为更新时间
        Object modifyTime = getFieldValByName("updateTime", metaObject);
        if (Objects.isNull(modifyTime)) {
            setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
        }
    }
}

对应功能如下:

@Service
@Validated
public class GoodsStoragePlanServiceImpl implements GoodsStoragePlanService {

    @Resource
    private GoodsStoragePlanMapper goodsStoragePlanMapper;

    @Resource
    private AdminUserService userService;


    @Override
    public Long createGoodsStoragePlan(GoodsStoragePlanSaveReqVO createReqVO) {
        // 插入
        GoodsStoragePlanDO goodsStoragePlan = BeanUtils.toBean(createReqVO, GoodsStoragePlanDO.class);
        
        goodsStoragePlan.setCreateTime(LocalDateTime.now());
        goodsStoragePlan.setUpdateTime(null);
        
        // 禁用全局字段填充
        FieldFillContext.setEnableFieldFill(false);
        try {
            goodsStoragePlanMapper.insert(goodsStoragePlan);
        } finally {
            // 恢复全局字段填充
            FieldFillContext.clear();
        }

        // 返回
        return goodsStoragePlan.getId();
    }
}

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

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

相关文章

nginx ws长连接配置

nginx ws长连接配置 http根节点下配上 map $http_upgrade $connection_upgrade {default upgrade; close;}如下&#xff1a; server服务节点下&#xff0c;后端接口的代理配置 proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connec…

电脑意外出现user32.dll丢失的八种修复方法,有效解决user32.dll文件丢失

遇到与 user32.dll 相关的错误通常是因为该文件已损坏、丢失、或者与某些软件冲突。今天这篇文章寄给大家介绍八种修复user32.dll丢失的方法&#xff0c;下面是一步步的详细教程来解决这个问题。 1. 重新启动电脑 第一步总是最简单的&#xff1a;重新启动你的电脑。许多小问题…

52.Python-web框架-Django - 多语言编译-fuzzy错误

目录 1.起因 2.原因 3.解决方法 3.1手动移除fuzzy标记 3.2重新生成po文件&#xff0c;并检查是否还存在fuzzy标记 3.3重新编译生成mo文件 1.起因 在Django的国际化和本地化过程中&#xff0c;当你发现某些字段仅显示msgid&#xff0c;而不显示msgstr时&#xff0c;可能是…

Python爬虫实战(实战篇)—18获取【小红书】首页信息写入Excel(仅用于学习-附完整版代码)

文章目录 专栏导读背景1、分析首页页面2、分析获取信息2-1,获取:笔记类型2-2,获取:标题2-3,获取:用户信息2-4,获取:用户ID2-5,获取:用户头像2-6,获取:文章连接完整代码总结专栏导读 文章名称链接Python爬虫实战(实战篇)—16获取【百度热搜】数据—写入Ecel(附完整…

日常销售数据分析为什么重要?三个维度全面分析日常销售数据

在当今电子商务的浪潮席卷全球的时代&#xff0c;网店如雨后春笋般涌现&#xff0c;并且竞争日趋激烈。在这样一个充满挑战与机遇的环境中&#xff0c;如何洞察市场动向&#xff0c;把握消费者需求&#xff0c;实现销售业绩的稳步增长&#xff0c;成为每一位电商运营者必须面对…

2024.6.12 作业 xyt

今日课堂练习&#xff1a;vector构造函数 #include <iostream> #include <vector> using namespace std;void printVector(vector<int> &v) {vector<int>::iterator iter;for(iterv.begin(); iter ! v.end(); iter){cout << *iter <<…

扩散模型会成为深度学习的下一个前沿领域吗?

文章目录 一、说明二、 第 1 部分&#xff1a;了解扩散模型2.1 什么是扩散模型2.2 正向扩散2.3 反向扩散 三、他们的高成本四、扩散模型的用处五、为什么扩散模型如此出色六、第 2 部分&#xff1a;使用扩散模型生成6.1 用于自然语言处理和 LLM 的文本扩散6.2 音频视频生成6.3 …

【APP移动端自动化测试】第一节.环境配置和adb调试工具

文章目录 前言一、Java环境搭建二、AndroidSDK环境搭建三、Android模拟器安装四、adb调试工具基本介绍 4.1 adb构成和基本原理 4.2 adb获取包名&#xff0c;界面名 4.3 adb文件传输 4.4 adb获取app启动时间 4.5 adb获取手机日志 4.6 adb其他有关…

空间搜索geohash概述;redis的geo命令

概述 通常在一些2C业务场景中会根据用户的位置来搜索一些内容。通常提供位置搜索的都是直接通过redis/mongodb/es等中间件实现的。 但是这些中间件又是怎么实现位置搜索的呢&#xff1b; 查了一番资料&#xff0c;发现背后一个公共的算法Geohash。 搜索的时候可以根据距离对…

「C系列」C 指针及其应用案例

文章目录 一、C 指针1. 指针的定义2. 指针的初始化3. 指针的解引用4. 指针的运算5. 动态内存分配6. 指针的NULL初始化7. 指针作为函数参数和返回值8. 指针数组和数组指针9. 多级指针 二、C语言中有哪些内置的指针操作符三、常见应用案例1. 交换两个变量的值2. 数组与指针3. 字符…

SwiftUI中自定义Shape与AnimateableData的使用

上一篇文章主要介绍了一下在SwiftUI中如何自定义Shape&#xff0c;本篇文章主要介绍Shape中的 一个关键的属性AnimatableData&#xff0c;它用于定义可以被动画化的数据。通过实现 Animatable 协议&#xff0c;可以让自定义视图或图形响应动画变化。 AnimatableData 是 Animata…

flutter 环境搭建(windows)(先装 jdk 建议1.8起步)

1&#xff1a;先从 官网 下载一个合适版本的SDK 2&#xff1a;下载完成之后 解压到一个合适的盘符下面&#xff08;本文在 D 盘 3.10.0版本&#xff09; 3&#xff1b;双击 flutter_console.bat文件可以看到一些基本信息 4&#xff1a;配置环境 1.添加用户变量 FLUTTER_STORAGE…

DSSA(Domain-Specific Software Architecture)方法论

DSSA&#xff08;Domain-Specific Software Architecture&#xff09;方法论是一种针对特定问题领域的软件架构设计方法。在软件开发中&#xff0c;有些问题在特定领域是共通的&#xff0c;这些问题可以通过通用的抽象和解决方案来处理。DSSA方法论正是利用这一特点&#xff0c…

Vue3、Element Plus使用v-for循环el-form表单进行校验

在开发中遇到了这样一个需求 有一个form是通过v-for生成出来的&#xff0c;并且数量不确定&#xff0c;每个表单中的字段都需要做校验&#xff0c;就将自己的解决方法记录了下来。 完整代码如下 <template><div class"for-form"><el-button type&quo…

Class-Aware Self-Distillation for Remote SensingImage Scene Classification

这篇文章提出了一种新的蒸馏方式&#xff0c;由于遥感场景图像具有类间相似性和类内多样性的特点&#xff0c;这篇文章试图解决这个问题。通过三个共享权重的分支&#xff0c;同时输入三张图片&#xff0c;其中两张类别相同的图片&#xff0c;一张类别不同但地物特征相似的图片…

AcWing 477:神经网络 ← 拓扑排序+链式前向星

【题目来源】https://www.acwing.com/problem/content/479/【题目描述】 人工神经网络&#xff08;Artificial Neural Network&#xff09;是一种新兴的具有自我学习能力的计算系统&#xff0c;在模式识别、函数逼近及贷款风险评估等诸多领域有广泛的应用。 对神经网络的研究…

179.二叉树:合并二叉树(力扣)

代码解决 /*** 二叉树节点的定义。* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, Tre…

记录在京东云ICP备案全流程,适用于网站备案和APP备案

记录一下在京东云ICP备案全流程&#xff0c;本文适用用于网站域名备案和APP备案&#xff0c;域名备案和APP备案其实差不多&#xff0c;就是服务类型选择网站或APP的区别&#xff0c;阿腾云整理详细京东云服务器备案全流程&#xff0c;大家可以收藏下&#xff0c;非常详细的备案…

爬虫相关面试题

一&#xff0c;如何抓取一个网站&#xff1f; 1&#xff0c;去百度和谷歌搜一下这个网站有没有分享要爬取数据的API 2, 看看电脑网页有没有所需要的数据&#xff0c;写代码测试调查好不好拿&#xff0c;如果好拿直接开始爬取 3&#xff0c;看看有没有电脑能打开的手机网页&a…

Unity与Js通信交互

目录 1.Js给Unity传递消息 2.Unity给Js传递消息 简介: Unity 与 JavaScript 通信交互是指在 Unity 项目中实现与 JavaScript 代码进行数据交换和功能调用的过程。 在 Unity 中&#xff0c;可以通过特定的接口和技术来与外部的 JavaScript 环境进行连接。这使得 Unity 能够利…