多叉树+图实现简单业务流程

news2024/11/25 23:17:47

文章目录

    • 场景
    • 整体架构流程
    • 业务界面
    • 技术细节
    • 小结

场景

     这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念.

整体架构流程

在这里插入图片描述

     方案管理、预案管理构成任务流程的基础条件,告警信息关联预案配置构成事件,也就是流程启动的入口信息.

业务界面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

技术细节

     其实也没有什么特殊的技术,也就用到了多叉树、图、最长路径计算(深搜)等.

  • 多叉树
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zwmac
 */
@Data
public class MoreParentTreeNode<T> {
    /**
     * 节点的唯一标识符
     */
    private Long id;
    /**
     * 节点的名称
     */
    private String name;
    /**
     * 节点的索引
     */
    private Integer index;
    /**
     * 子节点list
     */
    private List<MoreParentTreeNode<T>> children;
    /**
     * 父节点list
     */
    private List<MoreParentTreeNode<T>> parents;

    /**
     * 扩展信息(一般就存节点对应的原始数据)
     */
    private T extendInfo;

    public MoreParentTreeNode(Long id, String name,Integer index) {
        this.id = id;
        this.name = name;
        this.index = index;
        this.children = new ArrayList<>();
        this.parents = new ArrayList<>();
    }

    public MoreParentTreeNode() {

    }

    /**
     * 添加子节点
     * @param child 子节点
     */
    public void addChild(MoreParentTreeNode<T> child) {
        if (this.children.contains(child)) {
            return;
        }
        this.children.add(child);
        child.parents.add(this);
    }

    /**
     * 添加父节点
     * @param parent 父节点
     */
    public void addParent(MoreParentTreeNode<T> parent) {
        if (this.parents.contains(parent)) {
            return;
        }
        this.parents.add(parent);
        parent.children.add(this);
    }

    public void traverse() {
        System.out.println(this.name); // 打印节点名称

        if (this.children.isEmpty()) {
            System.out.println("没有子节点");
        }

        for (MoreParentTreeNode<T> child : this.children) {
            child.traverse(); // 递归遍历子节点
        }
    }

    public static void main(String[] args) {
        MoreParentTreeNode root = new MoreParentTreeNode(1L, "root",1);
        MoreParentTreeNode node1 = new MoreParentTreeNode(2L, "node1",2);
        MoreParentTreeNode node2 = new MoreParentTreeNode(3L, "node2",3);
        MoreParentTreeNode node3 = new MoreParentTreeNode(4L, "node3",4);
        MoreParentTreeNode node4 = new MoreParentTreeNode(5L, "node4",5);
        MoreParentTreeNode node5 = new MoreParentTreeNode(5L, "node5",6);
        MoreParentTreeNode node6 = new MoreParentTreeNode(6L, "node6",7);


        root.addChild(node1);
        root.addChild(node2);
        node1.addChild(node6);
        node3.addParent(node2);
        node4.addParent(node2);
        node4.addParent(node1);
        node5.addParent(node4);

        root.traverse();
    }
}

  • 图对象与计算基本方法
import lombok.Data;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 流程图对象,用于计算最长执行时间
 * @author zwmac
 */
@Data
public class ProcessGraph {
    /**
     * 顶点个数
     */
    private int numVertices;
    /**
     * 邻接表
     */
    private List<List<Integer>> adjList;
    /**
     * 顶点执行时间(边长)
     */
    private Double[] executionTimes;

    /**
     * 构造函数
     * @param numVertices 顶点个数
     * @param executionTimes 顶点执行时间(边长)
     */
    public ProcessGraph(int numVertices, Double[] executionTimes) {
        this.numVertices = numVertices;
        this.executionTimes = executionTimes;
        adjList = new ArrayList<>(numVertices);
        for (int i = 0; i < numVertices; i++) {
            adjList.add(new ArrayList<>());
        }
    }

    /**
     * 添加边
     * @param src 源顶点
     * @param dest 目标顶点
     */
    public void addEdge(int src, int dest) {
        adjList.get(src).add(dest);
    }

    /**
     * 计算最长执行时间
     * @return 最长执行时间
     */
    public Double findMaxExecutionTime() {
        Double[] maxExecutionTimes = new Double[numVertices];
        Arrays.fill(maxExecutionTimes, -1d);

        Double maxExecutionTime = 0d;

        for (int i = 0; i < numVertices; i++) {
            Double currentExecutionTime = dfs(i, maxExecutionTimes);
            if (currentExecutionTime > maxExecutionTime) {
                maxExecutionTime = currentExecutionTime;
            }
        }

        return maxExecutionTime;
    }

    /**
     * 深度优先搜索
     * @param vertex 顶点
     * @param maxExecutionTimes 最长执行时间数组
     * @return 最长执行时间
     */
    private Double dfs(int vertex, Double[] maxExecutionTimes) {
        if (maxExecutionTimes[vertex] != -1) {
            return maxExecutionTimes[vertex];
        }

        Double maxExecutionTime = executionTimes[vertex];

        for (int neighbor : adjList.get(vertex)) {
            Double neighborExecutionTime = dfs(neighbor, maxExecutionTimes);
            if (neighborExecutionTime + executionTimes[vertex] > maxExecutionTime) {
                maxExecutionTime = neighborExecutionTime + executionTimes[vertex];
            }
        }

        maxExecutionTimes[vertex] = maxExecutionTime;
        return maxExecutionTime;
    }

    public static void main(String[] args) {
        Double[] executionTimes = {0d,3d, 4d, 5d, 2d, 3d,0d};

        ProcessGraph graph = new ProcessGraph(7, executionTimes);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 3);
        graph.addEdge(1, 4);
        graph.addEdge(2, 3);
        graph.addEdge(3, 5);
        graph.addEdge(3, 5);
        //graph.addEdge(3, 5);

        Double maxExecutionTime = graph.findMaxExecutionTime();
        System.out.println("Max execution time: " + maxExecutionTime);
    }
}

  • 多叉树工具类
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.smartPark.business.emergency.event.entity.EmergencyEventTask;
import com.smartPark.business.emergency.event.entity.dto.EmergencyEventTaskDTO;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * @author zwmac
 */
public class EventTaskTreeUtil {
    /**
     * 转换流程树
     * @param eventTaskList 任务列表
     * @return 流程树根节点
     */
    public static MoreParentTreeNode<?> convertTreeDto(List<EmergencyEventTaskDTO> eventTaskList) {
        MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);
        EmergencyEventTaskDTO rootEventTaskDto = new EmergencyEventTaskDTO();
        rootEventTaskDto.setTaskDuration(0d);

        Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();
        for (int i = 0;i < eventTaskList.size();i++) {
            EmergencyEventTaskDTO eventTask = eventTaskList.get(i);
            Long id = eventTask.getId();
            String name = eventTask.getTaskName();
            MoreParentTreeNode<EmergencyEventTaskDTO> node = new MoreParentTreeNode<>(id,name,(i+1));
            node.setExtendInfo(eventTask);
            nodeMap.put(eventTask.getId(), node);
        }

        //处理父子关系(正)
        for (EmergencyEventTaskDTO eventTask : eventTaskList) {
            transNodeRef(eventTask, nodeMap, root);
        }

        //处理父子关系(反)
        for (int i = eventTaskList.size() - 1;i > -1; i--){
            EmergencyEventTaskDTO eventTask = eventTaskList.get(i);

            transNodeRef(eventTask, nodeMap, root);
        }


        return root;
    }

    /**
     * 转换流程树
     * @param eventTaskList 任务列表
     * @return 流程树根节点
     */
    public static MoreParentTreeNode<?> convertTree(List<EmergencyEventTask> eventTaskList) {
        MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);
        EmergencyEventTask rootEventTask = new EmergencyEventTask();
        rootEventTask.setTaskDuration(0d);

        Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();
        for (int i = 0;i < eventTaskList.size();i++) {
            EmergencyEventTask eventTask = eventTaskList.get(i);
            Long id = eventTask.getId();
            String name = eventTask.getTaskName();
            MoreParentTreeNode<EmergencyEventTask> node = new MoreParentTreeNode<>(id,name,(i+1));
            node.setExtendInfo(eventTask);
            nodeMap.put(eventTask.getId(), node);
        }

        //处理父子关系(正)
        for (EmergencyEventTask eventTask : eventTaskList) {
            EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();
            BeanUtil.copyProperties(eventTask, eventTaskDTO);
            transNodeRef(eventTaskDTO, nodeMap, root);

        }

        //处理父子关系(反)
        for (int i = eventTaskList.size() - 1;i > -1; i--){
            EmergencyEventTask eventTask = eventTaskList.get(i);
            EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();
            BeanUtil.copyProperties(eventTask,eventTaskDTO);
            transNodeRef(eventTaskDTO, nodeMap, root);
        }


        return root;
    }

    /**
     * 转换节点关系
     * @param eventTask 父级任务
     * @param nodeMap 节点map
     * @param root 根节点
     */
    private static void transNodeRef(EmergencyEventTaskDTO eventTask, Map<Long, MoreParentTreeNode<?>> nodeMap, MoreParentTreeNode<?> root) {
        Long id = eventTask.getId();
        MoreParentTreeNode<?> node = nodeMap.get(id);
        if (node != null){
            EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();
            Object eventTaskObj = node.getExtendInfo();
            if(eventTaskObj instanceof EmergencyEventTask) {
                EmergencyEventTask nodeEventTask = (EmergencyEventTask) eventTaskObj;
                BeanUtil.copyProperties(nodeEventTask, eventTaskDTO);
            }
            String preIds = eventTaskDTO.getBeforeTaskIds();
            if (StringUtils.isEmpty(preIds)){
                node.addParent(root);
                root.addChild(node);
            }else {
                String[] preIdsArr = preIds.split(",");
                if (ArrayUtil.isNotEmpty(preIdsArr)){
                    for (String preId : preIdsArr) {
                        MoreParentTreeNode<?> preNode = nodeMap.get(Long.valueOf(preId));
                        //前置任务不为空
                        if (preNode != null){
                            preNode.addChild(node);
                        }
                    }
                }
            }
            nodeMap.put(id, node);

        }
    }

    /**
     * 获取节点的子节点列表
     * @param treeNode 节点
     * @param eventTaskId 事件任务id
     * @return 子节点列表
     */
    public static List<? extends MoreParentTreeNode<?>> getNodeChildrenList(MoreParentTreeNode<?> treeNode, Long eventTaskId) {

        List<? extends MoreParentTreeNode<?>> childrenList = itGetNodeChildrenList(treeNode, eventTaskId);
        if (CollectionUtil.isNotEmpty(childrenList)) {
            return childrenList;
        }
        childrenList = itGetNodeParentList(treeNode, eventTaskId);
        return childrenList;
    }

    /**
     * 迭代获取节点的父节点列表
     * @param moreParentTreeNode 父节点
     * @param eventTaskId 事件任务id
     * @return 父节点列表
     */
    private static List<? extends MoreParentTreeNode<?>> itGetNodeParentList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {
        List<? extends MoreParentTreeNode<?>> children = null;
        if (moreParentTreeNode.getId().equals(eventTaskId)){
            children = moreParentTreeNode.getChildren();
        }else{
            List<? extends MoreParentTreeNode<?>> parentTreeNodes = moreParentTreeNode.getParents();
            if(CollectionUtil.isNotEmpty(parentTreeNodes)) {
                for (MoreParentTreeNode<?> parentTreeNode : parentTreeNodes) {
                    children = itGetNodeParentList(parentTreeNode, eventTaskId);
                    if (CollectionUtil.isNotEmpty(children)) {
                        break;
                    }
                }
            }
        }
        return children;
    }

    /**
     * 迭代获取节点的子节点列表
     * @param moreParentTreeNode 子节点
     * @param eventTaskId 事件任务id
     * @return 子节点列表
     */
    private static List<? extends MoreParentTreeNode<?>> itGetNodeChildrenList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {
        List<? extends MoreParentTreeNode<?>> children = null;
        if (moreParentTreeNode.getId().equals(eventTaskId)){
            children = moreParentTreeNode.getChildren();
        }else{
            List<? extends MoreParentTreeNode<?>> childrenChild = moreParentTreeNode.getChildren();
            if(CollectionUtil.isNotEmpty(childrenChild)) {
                for (MoreParentTreeNode<?> parentTreeNode : childrenChild) {
                    children = itGetNodeChildrenList(parentTreeNode, eventTaskId);
                    if (CollectionUtil.isNotEmpty(children)) {
                        break;
                    }
                }
            }
        }
        return children;

    }

    public static void main(String[] args) {
        List<EmergencyEventTaskDTO> eventTaskList = new ArrayList<>();
        initTestData(eventTaskList,9);
        System.out.println(JSONUtil.toJsonStr(eventTaskList));

        MoreParentTreeNode<?> treeNode = convertTreeDto(eventTaskList);

        List<?> childrenList = getNodeChildrenList(treeNode, 3L);

        treeNode.traverse();

    }

    private static void initTestData(List<EmergencyEventTaskDTO> eventTaskList, int num) {
        for (int i = 0;i < num; i++){
            EmergencyEventTaskDTO eventTask = new EmergencyEventTaskDTO();
            eventTask.setId(Long.valueOf(i));
            eventTask.setTaskName("任务-" + (i + 1));
            eventTask.setTaskDuration(RandomUtil.randomDouble(1,10));
            if (i > 1){
                StringJoiner sj = new StringJoiner(",");
                int n = RandomUtil.randomInt(0,i);
                for(int j = 0;j < i - 1 - n;j++){
                    sj.add(String.valueOf(j+1));
                }
                eventTask.setBeforeTaskIds(sj.toString());
            }
            eventTaskList.add(eventTask);
        }
    }


}

     使用思路也特别简单,实际就是将配置任务时只选择了节点上级任务的所有任务构成一个多叉树,然后跟这个树每个节点设置唯一编号,然后转化为图,计算最长路径作为事件的预计完成时间,最后就是各个任务节点的流转了.

/**
     * 计算预计完成时间
     * @param event 事件
     * @return 预计完成时间
     */
    private Date calPlanEndTime(EmergencyEvent event) {
        Date planStartTime = event.getPlanStartTime();
        Date finalNow = planStartTime;
        //先查询预案任务
        QueryWrapper<EmergencyEventPlan> eventPlanQw = new QueryWrapper<>();
        eventPlanQw.eq("event_id_", event.getId());
        List<EmergencyEventPlan> eventPlanList = eventPlanService.list(eventPlanQw);
        if (CollectionUtil.isNotEmpty(eventPlanList)){
            //最终预计完成时间
            //再查询预案任务
            for (int i = 0;i < eventPlanList.size();i++){
                QueryWrapper<EmergencyEventTask> eventTaskQw = new QueryWrapper<>();
                eventTaskQw.eq("event_id_", event.getId());
                eventTaskQw.eq("event_plan_id_", eventPlanList.get(i).getId());
                List<EmergencyEventTask> eventTaskList = eventPlanTaskService.list(eventTaskQw);
                if (CollectionUtil.isNotEmpty(eventTaskList)){

                    //再组织流程树
                    MoreParentTreeNode<?> processTree = EventTaskTreeUtil.convertTree(eventTaskList);

                    //最后计算时间
                    Date planTaskEndTime = calEndTime(processTree, planStartTime, eventTaskList);
                    if (planTaskEndTime.after(finalNow)){
                        finalNow = planTaskEndTime;
                    }
                }

            }

        }

        return finalNow;
    }

小结

  • 后悔数据结构没有学好
  • 算法用的少,也有很大工具包直接提供,但是还是很有学的必要的
         就随便写写,希望能帮到大家,uping!

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

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

相关文章

Redis学习第九天

今天是Jedis&#xff01;作者的Redis在游戏本上&#xff0c;但是Java的IDEA总是下载不了&#xff0c;所以只能作为概念听一听了&#xff0c;目前无法做到实操。 Jedis概念 Jedis实操 首先要保证redis的服务器开启&#xff0c;然后引入jedis依赖&#xff0c;最后通过服务器的I…

【学习笔记】深度学习分布式系统

深度学习分布式系统 前言1. 数据并行&#xff1a;参数服务器2. 流水线并行&#xff1a;GPipe3. 张量并行&#xff1a;Megatron LM4. 切片并行&#xff1a;ZeRO5. 异步分布式&#xff1a;PATHWAYS总结参考链接 前言 最近跟着李沐老师的视频学习了深度学习分布式系统的发展。这里…

作用域 CSS 回来了

几年前&#xff0c;消失的作用域 CSS&#xff0c;如今它回来了&#xff0c;而且比以前的版本要好得多。 更好的是&#xff0c;W3C规范基本稳定&#xff0c;现在Chrome中已经有一个工作原型。我们只需要社区稍微关注一下&#xff0c;引诱其他浏览器构建它们的实现&#xff0c;并…

嵌入式数据库sqlite3基本命令操作基础(05)

前言 数据在实际工作中应用非常广泛&#xff0c;数据库的产品也比较多&#xff0c;oracle、DB2、SQL2000、mySQL&#xff1b;基于嵌入式linux的数据库主要有SQLite, Firebird, Berkeley DB, eXtremeDB。 本文主要讲解数据库SQLite&#xff0c;通过这个开源的小型的嵌入式数据…

MySQL5.7高级函数:JSON_ARRAYAGG和JSON_OBJECT的使用

前置准备 DROP TABLE IF EXISTS t_user; CREATE TABLE t_user (id bigint(20) NOT NULL,name varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci …

Unity - 实践: Metallic流程贴图 转 Specular流程贴图

文章目录 目的Metallic Flow - SP - 输出输出的 MRA (MGA) 贴图 Metallic->Specular (根据教程一步一步实践)1. Base color Metallic -> Diffuse2. Base color Metallic -> Specular3. Roughness -> Glossiness输出贴图&#xff0c;在 unity 中展示&#xff1a;M…

内网测速工具-LibreSpeed

github链接&#xff1a; https://github.com/librespeed/speedtest-android

如何系列 如何使用OpenCV进行图像操作

文章目录 简介集成代码示例加载和显示图像编辑和保存图像边缘检测图片属性图像旋转图像缩放图像拼接颜色空间转换图像模糊平滑化腐蚀和膨胀直方图均衡化图像分割模板匹配图像特征提取图像拟合图像标注轮廓检测背景减除图像混合颜色分割图像旋转裁剪在图像上写文字检测和裁剪人脸…

大模型Tuning分类

类型总结 微调&#xff08;Fine-tunning&#xff09; 语言模型的参数需要一起参与梯度更新 轻量微调&#xff08;lightweight fine-tunning&#xff09; 冻结了大部分预训练参数&#xff0c;仅添加任务层&#xff0c;语言模型层参数不变 适配器微调 &#xff08;Adapter-t…

Java项目-文件搜索工具

目录 项目背景 项目效果 SQLite的下载安装 使用JDBC操作SQLite 第三方库pinyin4j pinyin4j的具体使用 封装pinyin4j 数据库的设计 创建实体类 实现DBUtil 封装FileDao 设计scan方法 多线程扫描 周期性扫描 控制台版本的客户端 图形化界面 设计图形化界面 项目…

PyTorch - 模型训练损失 (Loss) NaN 问题的解决方案

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/133378367 在模型训练中&#xff0c;如果出现 NaN 的问题&#xff0c;严重影响 Loss 的反传过程&#xff0c;因此&#xff0c;需要加入一些微小值…

什么是 Spring Boot?与传统 Spring 框架的区别

什么是 Spring Boot&#xff1f;与传统 Spring 框架的区别 引言 Spring框架是一个在Java应用程序开发中广泛使用的框架&#xff0c;它提供了一种构建企业级Java应用程序的强大方式。然而&#xff0c;Spring框架在一些方面存在复杂性和繁琐的配置&#xff0c;这促使Spring社区…

leetcode:561. 数组拆分(python3解法)

难度&#xff1a;简单 给定长度为 2n 的整数数组 nums &#xff0c;你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) &#xff0c;使得从 1 到 n 的 min(ai, bi) 总和最大。 返回该 最大总和 。 示例 1&#xff1a; 输入&#xff1a;nums [1,4,3,2] 输出…

软件设计模式系列之十八——迭代器模式

1 模式的定义 迭代器模式是一种行为型设计模式&#xff0c;它允许客户端逐个访问一个聚合对象中的元素&#xff0c;而不暴露该对象的内部表示。迭代器模式提供了一种统一的方式来遍历不同类型的集合&#xff0c;使客户端代码更加简洁和可复用。 2 举例说明 为了更好地理解迭…

护眼灯买哪种好?分享五款护眼灯

家里顶灯太暗了且高度太高&#xff0c;还是原始的LED灯&#xff0c;晚上用着眼睛都有点难受&#xff0c;还好遇到了儿童护眼灯。下面小编为大家介绍下儿童护眼灯哪个牌子好&#xff1f;什么护眼台灯比较专业 1、色温 台灯的色温也是一个需要考虑的因素&#xff0c;所谓的色温其…

数学建模常用模型

作为数学建模的编程手还掌握一些各类模型常用算法&#xff0c;数学建模评价类模型、分类模型、预测类模型比较常用的方法总结如下&#xff1a; 接下来对这些比较典型的模型进行详细进行介绍说明。 一、评价模型 在数学建模中&#xff0c;评价模型是比较基础的模型之一&#x…

云部署家里的服务器

1.固定静态ip 查看ip地址&#xff0c;en开头的 ifconfig查看路由器ip&#xff0c;via开头的 ip route修改配置文件 cd /etc/netplan/ #来到这个文件夹 sudo cp 01-network-manager-all.yaml 01-network-manager-all.yaml.bak #先备…

ChatGPT架构师:语言大模型的多模态能力、幻觉与研究经验

来源 | The Robot Brains Podcast OneFlow编译 翻译&#xff5c;宛子琳、杨婷 9月26日&#xff0c;OpenAI宣布ChatGPT新增了图片识别和语音能力&#xff0c;使得ChatGPT不仅可以进行文字交流&#xff0c;还可以给它展示图片并进行互动&#xff0c;这是一次ChatGPT向多模态进化的…

基于微信小程序的物流快递信息查询平台同城急送小程序(亮点:寄件、发票申请、在线聊天)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

css画一条0.5px的直线

【css】画一条0.5px的直线_css画一条0.5px的直线_a堅強的泡沫的博客-CSDN博客 一些用来装饰的图形&#xff0c;比如字体的前面或者后面设置图标&#xff0c;画一条线 如果要画很细很细的线&#xff0c;0.5px ,怎么画 用到css3的transform的scaleY(0.5)函数&#xff0c;对象图…