基于DDD的微服务落地

news2025/7/19 9:39:34

DDD四层架构

对实时性要求高的强一致性业务场景,可采取分布式事务。分布式事务有性能代价,在设计时需要平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。

领域事件驱动的异步方式是分布式架构常用的设计方式,可以解决非实时性场景下的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好的解耦微服务。通过削峰填谷,实现读写分离、减轻数据库实时访问压力,提高业务吞吐量和业务处理能力。

DDD设计思想和方法

DDD知识领域

主要内容

聚合的管理

聚合根、实体和值对象的关系

聚合数据的初始化和持久化

工厂模式和仓储模式

聚合的解耦

聚合代码的解耦、跨聚合的服务调用和对象解耦

领域事件管理

领域事件实体结构、持久化和事件发布

DDD分层架构

基础层、领域层、应用层和用户接口层的协作

服务的分层与协作

实体方法、领域服务、应用服务、facade接口服务,服务的组合和编排,跨多个聚合的服务管理和协调

对象的分层和转换

DTO、DO、PO等对象在不同层的转换和实现过程

微服务间的访问

登录和认证服务

聚合根

聚合根包括聚合根属性、关联的实体和值对象以及自身的业务行为等。通过引用实体和值对象,协调聚合内的多个实体,在聚合根类方法中完成多实体的复杂业务逻辑。

充血模型:即领域模型模式。有自己的业务行为(方法),如下充血模型中提供getDuration、addHistoryApprovalInfo等方法。

贫血模型:即事务脚本模式。只有对象属性和setter/getter。

@Data
public class Leave {
    String id;
    Applicant applicant;
    Approver approver;
    LeaveType type;
    Status status;
    Date startTime;
    Date endTime;
    long duration;
    //审批领导的最大级别
    int leaderMaxLevel;
    ApprovalInfo currentApprovalInfo;
    List<ApprovalInfo> historyApprovalInfos;

    public long getDuration() {
        return endTime.getTime() - startTime.getTime();
    }

    public Leave addHistoryApprovalInfo(ApprovalInfo approvalInfo) {
        if (null == historyApprovalInfos)
            historyApprovalInfos = new ArrayList<>();
        this.historyApprovalInfos.add(approvalInfo);
        return this;
    }

    public Leave create(){
        this.setStatus(Status.APPROVING);
        this.setStartTime(new Date());
        return this;
    }

    public Leave agree(Approver nextApprover){
        this.setStatus(Status.APPROVING);
        this.setApprover(nextApprover);
        return this;
    }

    public Leave reject(Approver approver){
        this.setApprover(approver);
        this.setStatus(Status.REJECTED);
        this.setApprover(null);
        return this;
    }

    public Leave finish(){
        this.setApprover(null);
        this.setStatus(Status.APPROVED);
        this.setEndTime(new Date());
        this.setDuration(this.getEndTime().getTime() - this.getStartTime().getTime());
        return this;
    }
}

实体

实体有自己的属性和关联的值对象。

@Data
public class ApprovalInfo {
    String approvalInfoId;
    Approver approver;
    ApprovalType approvalType;
    String msg;
    long time;
}

值对象

public enum Status {
    APPROVING, APPROVED, REJECTED
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Approver {

    String personId;
    String personName;
    int level;

    public static Approver fromPerson(Person person){
        Approver approver = new Approver();
        approver.setPersonId(person.getPersonId());
        approver.setPersonName(person.getPersonName());
        approver.setLevel(person.getRoleLevel());
        return approver;
    }

}

领域对象

如果一个业务行为由多个实体对象参与完成,就将该业务逻辑放在领域服务中实现。

实体方法:完成单一实体自身的业务逻辑,是相对简单的原子业务逻辑。

领域服务:由多个实体组合的相对复杂的业务逻辑。

@Service
@Slf4j
public class LeaveDomainService {
    @Autowired
    EventPublisher eventPublisher;
    @Autowired
    ILeaveRepository iLeaveRepository;
    @Autowired
    LeaveFactory leaveFactory;

    @Transactional
    public void createLeave(Leave leave, int leaderMaxLevel, Approver approver) {
        leave.setLeaderMaxLevel(leaderMaxLevel);
        leave.setApprover(approver);
        leave.create();
        iLeaveRepository.save(leaveFactory.createLeavePO(leave));
        LeaveEvent event = LeaveEvent.create(LeaveEventType.CREATE_EVENT, leave);
        iLeaveRepository.saveEvent(leaveFactory.createLeaveEventPO(event));
        eventPublisher.publish(event);
    }

    @Transactional
    public void updateLeaveInfo(Leave leave) {
        LeavePO po = leaveRepositoryInterface.findById(leave.getId());
        if (null == po) {
            throw new RuntimeException("leave不存在");
        }
        iLeaveRepository.save(leaveFactory.createLeavePO(leave));
    }

    @Transactional
    public void submitApproval(Leave leave, Approver approver) {
        LeaveEvent event;
        if ( ApprovalType.REJECT == leave.getCurrentApprovalInfo().getApprovalType()) {
            leave.reject(approver);
            event = LeaveEvent.create(LeaveEventType.REJECT_EVENT, leave);
        } else {
            if (approver != null) {
                leave.agree(approver);
                event = LeaveEvent.create(LeaveEventType.AGREE_EVENT, leave);
            } else {
                leave.finish();
                event = LeaveEvent.create(LeaveEventType.APPROVED_EVENT, leave);
            }
        }
        leave.addHistoryApprovalInfo(leave.getCurrentApprovalInfo());
        iLeaveRepository.save(leaveFactory.createLeavePO(leave));
        iLeaveRepository.saveEvent(leaveFactory.createLeaveEventPO(event));
        eventPublisher.publish(event);
    }

    public Leave getLeaveInfo(String leaveId) {
        LeavePO leavePO = iLeaveRepository.findById(leaveId);
        return leaveFactory.getLeave(leavePO);
    }

    public List<Leave> queryLeaveInfosByApplicant(String applicantId) {
        List<LeavePO> leavePOList = iLeaveRepository.queryByApplicantId(applicantId);
        return leavePOList.stream()
                .map(leavePO -> leaveFactory.getLeave(leavePO))
                .collect(Collectors.toList());
    }

    public List<Leave> queryLeaveInfosByApprover(String approverId) {
        List<LeavePO> leavePOList = iLeaveRepository.queryByApproverId(approverId);
        return leavePOList.stream()
                .map(leavePO -> leaveFactory.getLeave(leavePO))
                .collect(Collectors.toList());
    }
}

在应用服务组合不同聚合的领域服务时,通过Id或参数传参,尽量避免领域对象传参,以减少聚合之间的耦合度。

领域事件

领域事件基类

@Data
public class DomainEvent {
    String id;
    Date timestamp;
    String source;
    String data;
}
@Service
public class EventPublisher {
    public void publish(LeaveEvent event){
        //send to MQ
        //mq.send(event);
    }
}

领域事件实体

@Data
public class LeaveEvent extends DomainEvent {

    LeaveEventType leaveEventType;

    public static LeaveEvent create(LeaveEventType eventType, Leave leave){
        LeaveEvent event = new LeaveEvent();
        event.setId(IdGenerator.nextId());
        event.setLeaveEventType(eventType);
        event.setTimestamp(new Date());
        event.setData(JSON.toJSONString(leave));
        return event;
    }
}

领域事件的执行逻辑:

  1. 执行业务逻辑,产生领域事件。

  1. 调用仓储接口,完成业务数据持久化。

  1. 调用仓储接口,完成事件数据持久化。

  1. 完成领域事件发布。

仓储模式

仓储接口

public interface ILeaveRepository {

    void save(LeavePO leavePO);

    void saveEvent(LeaveEventPO leaveEventPO);

    LeavePO findById(String id);

    List<LeavePO> queryByApplicantId(String applicantId);

    List<LeavePO> queryByApproverId(String approverId);

}

仓储实现

@Repository
public class LeaveRepositoryImpl implements ILeaveRepository {
    @Autowired
    LeaveDao leaveDao;
    @Autowired
    ApprovalInfoDao approvalInfoDao;
    @Autowired
    LeaveEventDao leaveEventDao;

    public void save(LeavePO leavePO) {
        leaveDao.save(leavePO);
        leavePO.getHistoryApprovalInfoPOList().forEach(approvalInfoPO -> approvalInfoPO.setLeaveId(leavePO.getId()));
        approvalInfoDao.saveAll(leavePO.getHistoryApprovalInfoPOList());
    }

    public void saveEvent(LeaveEventPO leaveEventPO){
        leaveEventDao.save(leaveEventPO);
    }

    @Override
    public LeavePO findById(String id) {
        return leaveDao.findById(id)
                .orElseThrow(() -> new RuntimeException("leave不存在"));
    }

    @Override
    public List<LeavePO> queryByApplicantId(String applicantId) {
        List<LeavePO> leavePOList = leaveDao.queryByApplicantId(applicantId);
        leavePOList.forEach(leavePO -> {
                    List<ApprovalInfoPO> approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
                    leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
                });
        return leavePOList;
    }

    @Override
    public List<LeavePO> queryByApproverId(String approverId) {
        List<LeavePO> leavePOList = leaveDao.queryByApproverId(approverId);
        leavePOList.forEach(leavePO -> {
                    List<ApprovalInfoPO> approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
                    leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
                });
        return leavePOList;
    }

}

这里为什么没有使用一对多、多对对呢?时间紧任务重或者并发量不高时可以使用,后期并发量起来了后,数据库将成为瓶颈。

JPA/Hibernate注解:@OneToMany、@ManyToOne、@ManyToMany;

Mybatis注解

@Results(id="", value={@Result(column="", property="", jdbcType=JdbcType.INTEGER),

@Result(column="", property="", javaType=xx.class,one=@One(select="com.xx..XxMapper.selectById"))

})

@Results(id="",value = { @Result(property = "", column = ""),

@Result(property = "xxList", javaType=List.class, many=@Many(select=""), column = "")});

Mybatis xml: association、collection。

工厂模式

工厂模式将与业务无关的职能从聚合根中剥离,放在工厂中统一创建和初始化。

//可考虑使用MapStruct
@Service
public class LeaveFactory {
    public LeavePO createLeavePO(Leave leave) {
        LeavePO leavePO = new LeavePO();
        leavePO.setId(UUID.randomUUID().toString());
        leavePO.setApplicantId(leave.getApplicant().getPersonId());
        leavePO.setApplicantName(leave.getApplicant().getPersonName());
        leavePO.setApproverId(leave.getApprover().getPersonId());
        leavePO.setApproverName(leave.getApprover().getPersonName());
        leavePO.setStartTime(leave.getStartTime());
        leavePO.setStatus(leave.getStatus());
        List<ApprovalInfoPO> historyApprovalInfoPOList = approvalInfoPOListFromDO(leave);
        leavePO.setHistoryApprovalInfoPOList(historyApprovalInfoPOList);
        return leavePO;
    }

    public Leave getLeave(LeavePO leavePO) {
        Leave leave = new Leave();
        Applicant applicant = Applicant.builder()
                .personId(leavePO.getApplicantId())
                .personName(leavePO.getApplicantName())
                .build();
        leave.setApplicant(applicant);
        Approver approver = Approver.builder()
                .personId(leavePO.getApproverId())
                .personName(leavePO.getApproverName())
                .build();
        leave.setApprover(approver);
        leave.setStartTime(leavePO.getStartTime());
        leave.setStatus(leavePO.getStatus());
        List<ApprovalInfo> approvalInfos = getApprovalInfos(leavePO.getHistoryApprovalInfoPOList());
        leave.setHistoryApprovalInfos(approvalInfos);
        return leave;
    }

    public LeaveEventPO createLeaveEventPO(LeaveEvent leaveEvent){
        LeaveEventPO eventPO = new LeaveEventPO();
        eventPO.setLeaveEventType(leaveEvent.getLeaveEventType());
        eventPO.setSource(leaveEvent.getSource());
        eventPO.setTimestamp(leaveEvent.getTimestamp());
        eventPO.setData(JSON.toJSONString(leaveEvent.getData()));
        return eventPO;
    }

    private List<ApprovalInfoPO> approvalInfoPOListFromDO(Leave leave) {
        return leave.getHistoryApprovalInfos()
                .stream()
                .map(this::approvalInfoPOFromDO)
                .collect(Collectors.toList());
    }

    private ApprovalInfoPO approvalInfoPOFromDO(ApprovalInfo approvalInfo){
        ApprovalInfoPO po = new ApprovalInfoPO();
        po.setApproverId(approvalInfo.getApprover().getPersonId());
        po.setApproverLevel(approvalInfo.getApprover().getLevel());
        po.setApproverName(approvalInfo.getApprover().getPersonName());
        po.setApprovalInfoId(approvalInfo.getApprovalInfoId());
        po.setMsg(approvalInfo.getMsg());
        po.setTime(approvalInfo.getTime());
        return po;
    }

    private ApprovalInfo approvalInfoFromPO(ApprovalInfoPO approvalInfoPO){
        ApprovalInfo approvalInfo = new ApprovalInfo();
        approvalInfo.setApprovalInfoId(approvalInfoPO.getApprovalInfoId());
        Approver approver = Approver.builder()
                .personId(approvalInfoPO.getApproverId())
                .personName(approvalInfoPO.getApproverName())
                .level(approvalInfoPO.getApproverLevel())
                .build();
        approvalInfo.setApprover(approver);
        approvalInfo.setMsg(approvalInfoPO.getMsg());
        approvalInfo.setTime(approvalInfoPO.getTime());
        return approvalInfo;
    }

    private List<ApprovalInfo> getApprovalInfos(List<ApprovalInfoPO> approvalInfoPOList){
        return approvalInfoPOList.stream()
                .map(this::approvalInfoFromPO)
                .collect(Collectors.toList());
    }
}

服务的组合和编排

应用层的应用服务主要完成领域服务的组合与编排。

@Service
public class LeaveApplicationService {
    @Autowired
    LeaveDomainService leaveDomainService;
    @Autowired
    PersonDomainService personDomainService;
    @Autowired
    ApprovalRuleDomainService approvalRuleDomainService;

    /**
     * 创建一个请假申请并为审批人生成任务
     * @param leave
     */
    public void createLeaveInfo(Leave leave){
        int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(leave.getApplicant().getPersonType(), leave.getType().toString(), leave.getDuration());
        Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
        leaveDomainService.createLeave(leave, leaderMaxLevel, Approver.fromPerson(approver));
    }

    /**
     * 更新请假单基本信息
     * @param leave
     */
    public void updateLeaveInfo(Leave leave){
        leaveDomainService.updateLeaveInfo(leave);
    }

    /**
     * 提交审批,更新请假单信息
     * @param leave
     */
    public void submitApproval(Leave leave){
        //find next approver
        Person approver = personDomainService.findNextApprover(leave.getApprover().getPersonId(), leave.getLeaderMaxLevel());
        leaveDomainService.submitApproval(leave, Approver.fromPerson(approver));
    }

    public Leave getLeaveInfo(String leaveId){
        return leaveDomainService.getLeaveInfo(leaveId);
    }

    public List<Leave> queryLeaveInfosByApplicant(String applicantId){
        return leaveDomainService.queryLeaveInfosByApplicant(applicantId);
    }

    public List<Leave> queryLeaveInfosByApprover(String approverId){
        return leaveDomainService.queryLeaveInfosByApprover(approverId);
    }
}

服务接口的提供

facade门面接口主要抽象出Controller Api作为OpenFeign调用的接口门面类。

/**
 * @author lyonardo
 * @Description
 * @createTime 2020年03月08日 15:06:00
 */
@FeignClient(name = "leave-service", path = "/leave")
public interface LeaveFeignClient {
    @PostMapping("/submit")
    Response submitApproval(LeaveDTO leaveDTO);

    @PostMapping("/{leaveId}")
    Response findById(@PathVariable String leaveId);

    /**
     * 根据申请人查询所有请假单
     * @param applicantId
     * @return
     */
    @PostMapping("/query/applicant/{applicantId}")
    Response queryByApplicant(@PathVariable String applicantId);

    /**
     * 根据审批人id查询待审批请假单(待办任务)
     * @param approverId
     * @return
     */
    @PostMapping("/query/approver/{approverId}")
    Response queryByApprover(@PathVariable String approverId)();
}

实现类

@RestController
@RequestMapping("/leave")
@Slf4j
public class LeaveApi {

    @Autowired
    LeaveApplicationService leaveApplicationService;

    @PostMapping("/create")
    public Response createLeaveInfo(LeaveDTO leaveDTO){
        Leave leave = LeaveAssembler.toDO(leaveDTO);
        leaveApplicationService.createLeaveInfo(leave);
        return Response.ok();
    }

    @PutMapping("/update")
    public Response updateLeaveInfo(LeaveDTO leaveDTO){
        Leave leave = LeaveAssembler.toDO(leaveDTO);
        leaveApplicationService.updateLeaveInfo(leave);
        return Response.ok();
    }

    @PostMapping("/submit")
    public Response submitApproval(LeaveDTO leaveDTO){
        Leave leave = LeaveAssembler.toDO(leaveDTO);
        leaveApplicationService.submitApproval(leave);
        return Response.ok();
    }

    @PostMapping("/{leaveId}")
    public Response findById(@PathVariable String leaveId){
        Leave leave = leaveApplicationService.getLeaveInfo(leaveId);
        return Response.ok(LeaveAssembler.toDTO(leave));
    }

    /**
     * 根据申请人查询所有请假单
     * @param applicantId
     * @return
     */
    @PostMapping("/query/applicant/{applicantId}")
    public Response queryByApplicant(@PathVariable String applicantId){
        List<Leave> leaveList = leaveApplicationService.queryLeaveInfosByApplicant(applicantId);
        List<LeaveDTO> leaveDTOList = leaveList.stream().map(leave -> LeaveAssembler.toDTO(leave)).collect(Collectors.toList());
        return Response.ok(leaveDTOList);
    }

    /**
     * 根据审批人id查询待审批请假单(待办任务)
     * @param approverId
     * @return
     */
    @PostMapping("/query/approver/{approverId}")
    public Response queryByApprover(@PathVariable String approverId){
        List<Leave> leaveList = leaveApplicationService.queryLeaveInfosByApprover(approverId);
        List<LeaveDTO> leaveDTOList = leaveList.stream().map(leave -> LeaveAssembler.toDTO(leave)).collect(Collectors.toList());
        return Response.ok(leaveDTOList);
    }
}

数据组装层

LeaveAssembler

//可使用MapStruct做对象转换和组装
public class LeaveAssembler {
    public static LeaveDTO toDTO(Leave leave){
        LeaveDTO dto = new LeaveDTO();
        dto.setLeaveId(leave.getId());
        dto.setLeaveType(leave.getType().toString());
        dto.setStatus(leave.getStatus().toString());
        dto.setStartTime(DateUtil.formatDateTime(leave.getStartTime()));
        dto.setEndTime(DateUtil.formatDateTime(leave.getEndTime()));
        dto.setCurrentApprovalInfoDTO(ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo()));
        List<ApprovalInfoDTO> historyApprovalInfoDTOList = leave.getHistoryApprovalInfos()
                .stream()
                .map(historyApprovalInfo -> ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo()))
                .collect(Collectors.toList());
        dto.setHistoryApprovalInfoDTOList(historyApprovalInfoDTOList);
        dto.setDuration(leave.getDuration());
        return dto;
    }

    public static Leave toDO(LeaveDTO dto){
        Leave leave = new Leave();
        leave.setId(dto.getLeaveId());
        leave.setApplicant(ApplicantAssembler.toDO(dto.getApplicantDTO()));
        leave.setApprover(ApproverAssembler.toDO(dto.getApproverDTO()));
        leave.setCurrentApprovalInfo(ApprovalInfoAssembler.toDO(dto.getCurrentApprovalInfoDTO()));
        List<ApprovalInfo> historyApprovalInfoDTOList = dto.getHistoryApprovalInfoDTOList()
                .stream()
                .map(ApprovalInfoAssembler::toDO)
                .collect(Collectors.toList());
        leave.setHistoryApprovalInfos(historyApprovalInfoDTOList);
        return leave;
    }

}

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

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

相关文章

【python】使用python将360个文件夹里的照片,全部复制到指定的文件夹中,并且按照顺序重新命名

最近要做一个图像生成的课题&#xff0c;在网上找了一个混合的数据集。这个数据集中一共有360个文件夹&#xff0c;然后文件夹中有6-9张不等的照片&#xff0c;我的目标就是编写python代码将所有的照片取出来&#xff0c;放到一个指定的文件夹里&#xff0c;并且从1开始按照顺序…

yolov8行人识别教程(2023年毕业设计+源码)

yolov8识别视频直接上YOLOv8的结构图吧&#xff0c;小伙伴们可以直接和YOLOv5进行对比&#xff0c;看看能找到或者猜到有什么不同的地方&#xff1f; Backbone&#xff1a;使用的依旧是CSP的思想&#xff0c;不过YOLOv5中的C3模块被替换成了C2f模块&#xff0c;实现了进一步的轻…

VMware虚拟机安装Ubuntu 2022最新版详细图文安装教程(VMware虚拟机安装+Ubuntu下载+VMware虚拟机配置运行)

名人说:君子生非异也,善假于物也。——荀子 Code_流苏(CSDN) o(‐^▽^‐)o很高兴你打开了这篇博客,跟着步骤一步步尝试安装吧。✧ 目录 一、简单介绍二、安装虚拟机VMware三、Ubuntu镜像下载四、虚拟机VMware配置及运行★如有疑问,欢迎评论,博主看到即回!当然,期待你的…

【基础算法】双指针---判断子序列

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

极速开发,无限可能,2023网易低代码大赛全新赛季启动

去年火爆的低代码大赛还犹在目&#xff0c;近800人用轻舟低代码平台畅享开发乐趣。这不&#xff0c;2023网易低代码大赛即刻启动&#xff0c;3月6日至3月27日限时开放报名&#xff0c;全新角逐&#xff0c;正式展开&#xff01;1\ 获胜者可得万元大奖、猪厂工作机会 /Low Code …

JavaEE课程实践-Servlet的部署(tomcat服务器)

目录 Servlet简述 tomcat服务器的安装和运行 Servlet的部署 部署具体步骤 一、创建maven工程 二、创建Servlet类 三、导入相应jar包 四、编写Servlet代码 五、运行maven项目&#xff0c;启动tomcat服务器 六、测试访问是否成功。 Servlet简述 Servlet 是 Java EE 技术…

第六章:多线程

第六章&#xff1a;多线程 6.1&#xff1a;程序、进程、线程基本概念 程序 程序program是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码&#xff0c;静态对象。 进程 ​ 进程process是程序的一次执行过程&#xff0c;或是正在运行的一个程序。是一个…

vue3相比vue2性能上提升体现

vue3相比vue2&#xff0c;在 编译阶段&#xff0c;源码体积 响应式系统 都做了性能提升不以解决实际业务痛点的更新都是耍流氓1. ts的支持2. 移除了不常用的api,例如 过滤器 $on $off $once 实例方法 内联模板attribute $destroy3. 加了tree - shaking4. 移除了mixin缺点&#…

ESP32设备驱动-RFID-RC522模块驱动

RFID-RC522模块驱动 文章目录 RFID-RC522模块驱动1、RFID-RC522介绍2、硬件准备3、软件准备4、驱动实现1、RFID-RC522介绍 基于 NXP 的 MFRC522 IC 的 RC522 RFID 模块通常带有一个 RFID 卡标签和具有 1KB 内存的密钥卡标签。 最重要的是,它可以写一个标签,这样你就可以在里…

前端老赵一次给你讲透“微前端”架构

一、引言&#xff1a; 随着Web应用程序的规模和复杂度的不断增加&#xff0c;前端技术也在不断发展和演进。微前端是近年来兴起的一种前端架构模式&#xff0c;通过将大型Web应用程序拆分为小型、可独立开发和部署的模块&#xff0c;从而降低开发和维护的难度&#xff0c;同时…

【LeetCode与《代码随想录》】二叉树篇:做题笔记与总结-JavaScript版

文章目录代码随想录144. 二叉树的前序遍历94. 二叉树的中序遍历145. 二叉树的后序遍历102.二叉树的层序遍历226.翻转二叉树101. 对称二叉树104.二叉树的最大深度111.二叉树的最小深度222.完全二叉树的节点个数110.平衡二叉树257. 二叉树的所有路径404.左叶子之和513.找树左下角…

盘点曾经很火但消失了的8个软件

目录 1、飞信 3、暴风影音 4、千千静听 5、虾米音乐 6、快车下载 7、人人网 8、QQ农场 今天小编给大家分享曾经很火但消失了的8个软件&#xff0c;你都用过吗&#xff1f; 1、飞信 飞信是中国移动通信集团公司推出的一款短信、语音、视频通信应用程序。它于2007年推出&a…

JDK的动态代理(powernode 文档)(内含源代码)

JDK的动态代理&#xff08;powernode 文档&#xff09;&#xff08;内含源代码&#xff09; 源代码下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87546086 一、动态代理 目录JDK的动态代理&#xff08;powernode 文档&#xff09;&#xff0…

什么是L1和L2正则化,以及它们有什么区别

一、L1和L2正则化是什么&#xff1f; 在防止过拟合的方法中有L1正则化和L2正则化&#xff0c;L1和L2是正则化项&#xff0c;又叫做惩罚项&#xff0c;是为了限制模型的参数&#xff0c;防止模型过拟合而加在损失函数后面的一项。 在二维的情况下&#xff0c;黄色的部分是L2和…

【云原生】rancher2.6部署MySQL—2023.03

文章目录概要1. 准备NFS服务器1.1 安装nfs1.2 创建挂载路径1.3 启动NFS服务2. 所有node节点上安装NFS服务3. rancher上部署MySQL3.1 创建PV3.2 创建PVC3.3 创建服务发现3.4 部署MySQL服务4. 测试概要 本文以单master节点为例&#xff0c;部署mysql&#xff0c;多master&#x…

Ubutun设置SSH远程登录

Ubutun设置SSH远程登录一、安装ssh-server二、配置ssh三、防火墙配置一、安装ssh-server 在需要远程登录的设备中安装ssh-server sudo apt update sudo apt install openssh-server出现提示时&#xff0c;输入密码&#xff0c;然后按Enter继续安装。安装完毕后&#xff0c;使…

2023金三银四应届生求职面试指南

一、应届生优势 划重点&#xff0c;一定要走校招;千万不要等毕业之后再想着找工作&#xff0c;在毕业前就要敲定落实;否则&#xff0c;就真的该焦虑了。要知道应届生的身份是一个很吃香的身份;只有应届生可以走校园招聘。 1、那校园招聘跟社会招聘有多大的差距?? 这么说吧&…

微信聊天的一个创新方向

开门见山&#xff0c;简单&#xff08;简陋&#xff09;展示下新的聊天界面&#xff1a; 注意到除了原本的发送键&#xff0c;多了几个别的按钮。为了对比方便&#xff0c;先放上当前的聊天方式&#xff1a; 给兄弟发消息时&#xff0c;点击发送键&#xff0c;显示的是如下发…

【华为机试真题详解 Python实现】静态扫描最优成本【2023 Q1 | 100分】

文章目录前言题目描述输入描述输出描述示例 1输入&#xff1a;输出&#xff1a;示例 2输入&#xff1a;输出&#xff1a;题目解析参考代码前言 《华为机试真题详解》专栏含牛客网华为专栏、华为面经试题、华为OD机试真题。 如果您在准备华为的面试&#xff0c;期间有想了解的…

【微信小程序项目实战】TodoList-环境配置(1)

目录前言简介环境配置TDesign图片页面文件文件基础配置app.wxssapp.jsontodo.json前言 本项目依据开源项目:点击前往 GITHUB 仓库 仿照搭设而成&#xff0c;并主要对其中原理以及方法做出详细分析解读&#xff0c;望大家多多支持原作者&#xff01; 简介 本项目将使用最新版…