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

【开源项目】snakeflow流程引擎研究

项目地址

https://gitee.com/yuqs/snakerflow

https://toscode.mulanos.cn/zc-libre/snakerflow-spring-boot-stater (推荐)

https://github.com/snakerflow-starter/snakerflow-spring-boot-starter

常用API

部署流程

processId = engine.process().deploy(StreamHelper.getStreamFromClasspath("test/task/simple/leave.snaker"), null, 10001L);

创建流程实例

Order order = engine.startInstanceById(processId, "1", args);

执行任务

 List<Task> tasks = engine.executeTask(activeTasks.get(0).getId(), "1");

获取某个人的需审批任务

 List<Task> activeTasks2 = engine.query().getActiveTasks(new QueryFilter().setOperator("admin"));

请假流程xml配置

<process displayName="请假流程测试" instanceUrl="/snaker/flow/all" name="leave">
<start displayName="start1" layout="24,124,-1,-1" name="start1" postInterceptors="test.task.interceptor.LocalTaskInterceptor" preInterceptors="test.task.interceptor.LocalTaskInterceptor">
<transition g="" name="transition1" offset="0,0" to="apply"/>
</start>
<end displayName="end1" layout="570,124,-1,-1" name="end1"/>
<task assignee="apply.operator" displayName="请假申请" form="/flow/leave/apply" layout="117,122,-1,-1" name="apply" performType="ANY">
<transition g="" name="transition2" offset="0,0" to="approveDept"/>
</task>
<task assignee="approveDept.operator" displayName="部门经理审批" form="/flow/leave/approveDept" layout="272,122,-1,-1" name="approveDept" performType="ANY">
<transition g="" name="transition3" offset="0,0" to="decision1"/>
</task>
<decision displayName="decision1" expr="#day &gt; 2 ? 'transition5' : 'transition4'" layout="426,124,-1,-1" name="decision1">
<transition displayName="&lt;=2天" g="" name="transition4" offset="0,0" to="end1"/>
<transition displayName="&gt;2天" g="" name="transition5" offset="0,0" to="approveBoss"/>
</decision>
<task assignee="approveBoss.operator" displayName="总经理审批" form="/flow/leave/approveBoss" layout="404,231,-1,-1" name="approveBoss" performType="ANY">
<transition g="" name="transition6" offset="0,0" to="end1"/>
</task>
</process>

流程解析

创建流程实例

SnakerEngineImpl#startInstanceById(),获取StartModel对象,执行execute方法。

	public Order startInstanceById(String id, String operator, Map<String, Object> args) {if(args == null) args = new HashMap<String, Object>();Process process = process().getProcessById(id);process().check(process, id);return startProcess(process, operator, args);}private Order startProcess(Process process, String operator, Map<String, Object> args) {Execution execution = execute(process, operator, args, null, null);if(process.getModel() != null) {StartModel start = process.getModel().getStart();AssertHelper.notNull(start, "流程定义[name=" + process.getName() + ", version=" + process.getVersion() + "]没有开始节点");start.execute(execution);}return execution.getOrder();}

NodeModel#execute,执行拦截器和exec方法。

	public void execute(Execution execution) {intercept(preInterceptorList, execution);exec(execution);intercept(postInterceptorList, execution);}

StartModel#exec

	protected void exec(Execution execution) {runOutTransition(execution);}

NodeModel#runOutTransition,获取TransitionModel对象,执行TransitionModel#execute方法。

	/*** 运行变迁继续执行* @param execution 执行对象*/protected void runOutTransition(Execution execution) {for (TransitionModel tm : getOutputs()) {tm.setEnabled(true);tm.execute(execution);}}

TransitionModel#execute,如果下一个节点是TaskModel对象,执行CreateTaskHandler

	public void execute(Execution execution) {if(!enabled) return;if(target instanceof TaskModel) {//如果目标节点模型为TaskModel,则创建taskfire(new CreateTaskHandler((TaskModel)target), execution);} else if(target instanceof SubProcessModel) {//如果目标节点模型为SubProcessModel,则启动子流程fire(new StartSubProcessHandler((SubProcessModel)target), execution);} else {//如果目标节点模型为其它控制类型,则继续由目标节点执行target.execute(execution);}}

创建任务

CreateTaskHandler#handle

	/*** 根据任务模型、执行对象,创建下一个任务,并添加到execution对象的tasks集合中*/public void handle(Execution execution) {List<Task> tasks = execution.getEngine().task().createTask(model, execution);execution.addTasks(tasks);/*** 从服务上下文中查找任务拦截器列表,依次对task集合进行拦截处理*/List<SnakerInterceptor> interceptors = ServiceContext.getContext().findList(SnakerInterceptor.class);try {for(SnakerInterceptor interceptor : interceptors) {interceptor.intercept(execution);}} catch(Exception e) {log.error("拦截器执行失败=" + e.getMessage());throw new SnakerException(e);}}

TaskService#createTask,获取任务的执行者,根据performType来生成多个任务还是单个任务。

	public List<Task> createTask(TaskModel taskModel, Execution execution) {List<Task> tasks = new ArrayList<Task>();Map<String, Object> args = execution.getArgs();if(args == null) args = new HashMap<String, Object>();Date expireDate = DateHelper.processTime(args, taskModel.getExpireTime());Date remindDate = DateHelper.processTime(args, taskModel.getReminderTime());String form = (String)args.get(taskModel.getForm());String actionUrl = StringHelper.isEmpty(form) ? taskModel.getForm() : form;String[] actors = getTaskActors(taskModel, execution);args.put(Task.KEY_ACTOR, StringHelper.getStringByArray(actors));Task task = createTaskBase(taskModel, execution);task.setActionUrl(actionUrl);task.setExpireDate(expireDate);task.setExpireTime(DateHelper.parseTime(expireDate));task.setVariable(JsonHelper.toJson(args));if(taskModel.isPerformAny()) {//任务执行方式为参与者中任何一个执行即可驱动流程继续流转,该方法只产生一个tasktask = saveTask(task, actors);task.setRemindDate(remindDate);tasks.add(task);} else if(taskModel.isPerformAll()){//任务执行方式为参与者中每个都要执行完才可驱动流程继续流转,该方法根据参与者个数产生对应的task数量for(String actor : actors) {Task singleTask;try {singleTask = (Task) task.clone();} catch (CloneNotSupportedException e) {singleTask = task;}singleTask = saveTask(singleTask, actor);singleTask.setRemindDate(remindDate);tasks.add(singleTask);}}return tasks;}

TaskService#getTaskActors(),获取配置中的assignee,如果参数中有定义args.put("apply.operator", "1");,则是对应key的value值。

	private String[] getTaskActors(TaskModel model, Execution execution) {Object assigneeObject = null;AssignmentHandler handler = model.getAssignmentHandlerObject();if(StringHelper.isNotEmpty(model.getAssignee())) {assigneeObject = execution.getArgs().get(model.getAssignee());} else if(handler != null) {if(handler instanceof Assignment) {assigneeObject = ((Assignment)handler).assign(model, execution);} else {assigneeObject = handler.assign(execution);}}return getTaskActors(assigneeObject == null ? model.getAssignee() : assigneeObject);}

TaskService#getTaskActors(java.lang.Object),如果拿到的数据是带有英文逗号的字符串,会根据逗号做切割。

	private String[] getTaskActors(Object actors) {if(actors == null) return null;String[] results;if(actors instanceof String) {//如果值为字符串类型,则使用逗号,分隔return ((String)actors).split(",");} else if(actors instanceof List){//jackson会把stirng[]转成arraylist,此处增加arraylist的逻辑判断,by 红豆冰沙2014.11.21List<?> list = (List)actors;results = new String[list.size()];for(int i = 0; i < list.size(); i++) {results[i] = (String)list.get(i);}return results;} else if(actors instanceof Long) {//如果为Long类型,则返回1个元素的String[]results = new String[1];results[0] = String.valueOf((Long)actors);return results;} else if(actors instanceof Integer) {//如果为Integer类型,则返回1个元素的String[]results = new String[1];results[0] = String.valueOf((Integer)actors);return results;} else if(actors instanceof String[]) {//如果为String[]类型,则直接返回return (String[])actors;} else {//其它类型,抛出不支持的类型异常throw new SnakerException("任务参与者对象[" + actors + "]类型不支持."+ "合法参数示例:Long,Integer,new String[]{},'10000,20000',List<String>");}}

判断模型

DecisionModel#exec,获取模型中的expr属性,执行判断。根据执行结果决定执行哪一个TransitionModel

	public void exec(Execution execution) {log.info(execution.getOrder().getId() + "->decision execution.getArgs():" + execution.getArgs());if(expression == null) {expression = ServiceContext.getContext().find(Expression.class);}log.info("expression is " + expression);if(expression == null) throw new SnakerException("表达式解析器为空,请检查配置.");String next = null;if(StringHelper.isNotEmpty(expr)) {next = expression.eval(String.class, expr, execution.getArgs());} else if(decide != null) {next = decide.decide(execution);}log.info(execution.getOrder().getId() + "->decision expression[expr=" + expr + "] return result:" + next);boolean isfound = false;for(TransitionModel tm : getOutputs()) {if(StringHelper.isEmpty(next)) {String expr = tm.getExpr();if(StringHelper.isNotEmpty(expr) && expression.eval(Boolean.class, expr, execution.getArgs())) {tm.setEnabled(true);tm.execute(execution);isfound = true;}} else {if(tm.getName().equals(next)) {tm.setEnabled(true);tm.execute(execution);isfound = true;}}}if(!isfound) throw new SnakerException(execution.getOrder().getId() + "->decision节点无法确定下一步执行路线");}

解析xml成ProcessModel对象

ProcessService#deploy(),根据ModelParser进行解析,使用缓存避免二次解析造成时间浪费。

    public String deploy(InputStream input, String creator, Long tenantId) {AssertHelper.notNull(input);try {byte[] bytes = StreamHelper.readBytes(input);ProcessModel model = ModelParser.parse(bytes);Integer version = access().getLatestProcessVersion(model.getName());Process entity = new Process();entity.setId(StringHelper.getPrimaryKey());if (version == null || version < 0) {entity.setVersion(0);} else {entity.setVersion(version + 1);}entity.setState(STATE_ACTIVE);entity.setModel(model);entity.setBytes(bytes);entity.setCreateTime(DateHelper.getTime());entity.setCreator(creator);entity.setTenantId(tenantId);saveProcess(entity);cache(entity);return entity.getId();} catch (Exception e) {e.printStackTrace();log.error(e.getMessage());throw new SnakerException(e.getMessage(), e.getCause());}}

ModelParser#parse,获取xml中的节点数据

	public static ProcessModel parse(byte[] bytes) {DocumentBuilder documentBuilder = XmlHelper.createDocumentBuilder();if(documentBuilder != null) {Document doc = null;try {doc = documentBuilder.parse(new ByteArrayInputStream(bytes));Element processE = doc.getDocumentElement();ProcessModel process = new ProcessModel();process.setName(processE.getAttribute(NodeParser.ATTR_NAME));process.setDisplayName(processE.getAttribute(NodeParser.ATTR_DISPLAYNAME));process.setExpireTime(processE.getAttribute(NodeParser.ATTR_EXPIRETIME));process.setInstanceUrl(processE.getAttribute(NodeParser.ATTR_INSTANCEURL));process.setInstanceNoClass(processE.getAttribute(NodeParser.ATTR_INSTANCENOCLASS));NodeList nodeList = processE.getChildNodes();int nodeSize = nodeList.getLength();for(int i = 0; i < nodeSize; i++) {Node node = nodeList.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {NodeModel model = parseModel(node);process.getNodes().add(model);}}//循环节点模型,构造变迁输入、输出的source、targetfor(NodeModel node : process.getNodes()) {for(TransitionModel transition : node.getOutputs()) {String to = transition.getTo();for(NodeModel node2 : process.getNodes()) {if(to.equalsIgnoreCase(node2.getName())) {node2.getInputs().add(transition);transition.setTarget(node2);}}}}return process;} catch (SAXException e) {e.printStackTrace();throw new SnakerException(e);} catch (IOException e) {throw new SnakerException(e);}} else {throw new SnakerException("documentBuilder is null");}}

补充

数据操作

DBAccess数据库操作接口类,用于保存任务,流程和流程实例。AbstractDBAccess实现了DBAccess,定义了修改和插入数据的sql。如果想要做租户化定制,可以在AbstractDBAccess做修改。

/*** 数据库访问接口* 主要提供保存、更新、查询流程的相关table* @author yuqs* @since 1.0*/
public interface DBAccess {/*** 根据访问对象,设置具体的实现类* @param accessObject 数据库访问对象(Connection等)*/public void initialize(Object accessObject);/*** 保存任务对象* @param task 任务对象*/public void saveTask(Task task);/*** 保存流程实例对象* @param order 流程实例对象*/public void saveOrder(Order order);/*** 保存抄送实例* @param ccorder 抄送实体* @since 1.5*/public void saveCCOrder(CCOrder ccorder);/*** 保存流程定义对象* @param process 流程定义对象*/public void saveProcess(Process process);/*** 保存任务参与者对象* @param taskActor 任务参与者对象*/public void saveTaskActor(TaskActor taskActor);/*** 更新任务对象* @param task 任务对象*/public void updateTask(Task task);/*** 更新流程实例对象* @param order 流程实例对象*/public void updateOrder(Order order);/*** 更新抄送状态* @param ccorder 抄送实体对象*/public void updateCCOrder(CCOrder ccorder);/*** 更新流程定义对象* @param process 流程定义对象*/public void updateProcess(Process process);/*** 删除流程定义对象* @param process 流程定义对象*/public void deleteProcess(Process process);/*** 更新流程定义类别* @param type 类别* @since 1.5*/public void updateProcessType(String id, String type);/*** 删除任务、任务参与者对象* @param task 任务对象*/public void deleteTask(Task task);/*** 删除流程实例对象* @param order 流程实例对象*/public void deleteOrder(Order order);/*** 删除抄送记录* @param ccorder 抄送实体对象*/public void deleteCCOrder(CCOrder ccorder);/*** 删除参与者* @param taskId 任务id* @param actors 参与者集合*/public void removeTaskActor(String taskId, String... actors);/*** 迁移活动实例* @param order 历史流程实例对象*/public void saveHistory(HistoryOrder order);/*** 更新历史流程实例状态* @param order 历史流程实例对象*/public void updateHistory(HistoryOrder order);/*** 迁移活动任务* @param task 历史任务对象*/public void saveHistory(HistoryTask task);/*** 删除历史实例记录* @param historyOrder 历史实例*/public void deleteHistoryOrder(HistoryOrder historyOrder);/*** 删除历史任务记录* @param historyTask 历史任务*/public void deleteHistoryTask(HistoryTask historyTask);/*** 更新实例变量(包括历史实例表)* @param order 实例对象*/public void updateOrderVariable(Order order);/*** 保存委托代理对象* @param surrogate 委托代理对象*/public void saveSurrogate(Surrogate surrogate);/*** 更新委托代理对象* @param surrogate 委托代理对象*/public void updateSurrogate(Surrogate surrogate);/*** 删除委托代理对象* @param surrogate 委托代理对象*/public void deleteSurrogate(Surrogate surrogate);/*** 根据主键id查询委托代理对象* @param id 主键id* @return surrogate 委托代理对象*/public Surrogate getSurrogate(String id);/*** 根据授权人、流程名称查询委托代理对象* @param page 分页对象* @param filter 查询过滤器* @return List<Surrogate> 委托代理对象集合*/public List<Surrogate> getSurrogate(Page<Surrogate> page, QueryFilter filter);/*** 根据任务id查询任务对象* @param taskId 任务id* @return Task 任务对象*/public Task getTask(String taskId);/*** 根据任务ID获取历史任务对象* @param taskId 历史任务id* @return 历史任务对象*/HistoryTask getHistTask(String taskId);/*** 根据父任务id查询所有子任务* @param parentTaskId 父任务id* @return List<Task> 活动任务集合*/public List<Task> getNextActiveTasks(String parentTaskId);/*** 根据流程实例id、任务名称获取* @param orderId 流程实例id* @param taskName 任务名称* @param parentTaskId 父任务id* @return List<Task> 活动任务集合*/public List<Task> getNextActiveTasks(String orderId, String taskName, String parentTaskId);/*** 根据任务id查询所有活动任务参与者集合* @param taskId 活动任务id* @return List<TaskActor> 活动任务参与者集合*/public List<TaskActor> getTaskActorsByTaskId(String taskId);/*** 根据任务id查询所有历史任务参与者集合* @param taskId 历史任务id* @return List<HistoryTaskActor> 历史任务参与者集合*/public List<HistoryTaskActor> getHistTaskActorsByTaskId(String taskId);/*** 根据流程实例id查询实例对象* @param orderId 活动流程实例id* @return Order 活动流程实例对象*/public Order getOrder(String orderId);/*** 根据流程实例id、参与者id获取抄送记录* @param orderId 活动流程实例id* @param actorIds 参与者id* @return 传送记录列表*/public List<CCOrder> getCCOrder(String orderId, String... actorIds);/*** 根据流程实例ID获取历史流程实例对象* @param orderId 历史流程实例id* @return HistoryOrder 历史流程实例对象*/HistoryOrder getHistOrder(String orderId);/*** 根据流程定义id查询流程定义对象* @param id 流程定义id* @return Process 流程定义对象*/public Process getProcess(String id);/*** 根据流程名称查询最近的版本号* @param name 流程名称* @return Integer 流程定义版本号*/public Integer getLatestProcessVersion(String name);/*** 根据查询的参数,分页对象,返回分页后的查询结果* @param page 分页对象* @param filter 查询过滤器* @return List<Process> 流程定义集合*/public List<Process> getProcesss(Page<Process> page, QueryFilter filter);/*** 分页查询流程实例* @param page 分页对象* @param filter 查询过滤器* @return List<Order> 活动流程实例集合*/public List<Order> getActiveOrders(Page<Order> page, QueryFilter filter);/*** 分页查询活动任务列表* @param page 分页对象* @param filter 查询过滤器* @return List<Task> 活动任务集合*/public List<Task> getActiveTasks(Page<Task> page, QueryFilter filter);/*** 分页查询历史流程实例* @param page 分页对象* @param filter 查询过滤器* @return List<HistoryOrder> 历史流程实例集合*/public List<HistoryOrder> getHistoryOrders(Page<HistoryOrder> page, QueryFilter filter);/*** 根据参与者分页查询已完成的历史任务* @param page 分页对象* @param filter 查询过滤器* @return List<HistoryTask> 历史任务集合*/public List<HistoryTask> getHistoryTasks(Page<HistoryTask> page, QueryFilter filter);/*** 根据查询的参数,分页对象,返回分页后的活动工作项* @param page 分页对象* @param filter 查询过滤器* @return List<WorkItem> 活动工作项*/public List<WorkItem> getWorkItems(Page<WorkItem> page, QueryFilter filter);/*** 根据查询的参数,分页对象,返回分页后的抄送任务项* @param page 分页对象* @param filter 查询过滤器* @return List<WorkItem> 活动工作项*/public List<HistoryOrder> getCCWorks(Page<HistoryOrder> page, QueryFilter filter);/*** 根据流程定义ID、参与者分页查询已完成的历史任务项* @param page 分页对象* @param filter 查询过滤器* @return List<WorkItem> 历史工作项*/public List<WorkItem> getHistoryWorkItems(Page<WorkItem> page, QueryFilter filter);/*** 根据类型clazz、Sql语句、参数查询单个对象* @param clazz 类型* @param sql sql语句* @param args 参数列表* @return 结果对象*/public <T> T queryObject(Class<T> clazz, String sql, Object... args);/*** 根据类型clazz、Sql语句、参数查询列表对象* @param clazz 类型* @param sql sql语句* @param args 参数列表* @return 结果对象列表*/public <T> List<T> queryList(Class<T> clazz, String sql, Object... args);/*** 根据类型clazz、Sql语句、参数分页查询列表对象* @param page 分页对象* @param filter 查询过滤器* @param clazz 类型* @param sql sql语句* @param args 参数列表* @return 结果对象列表*/public <T> List<T> queryList(Page<T> page, QueryFilter filter, Class<T> clazz, String sql, Object... args);/*** 运行脚本文件*/public void runScript();
}

Context

public interface Context {/*** 根据服务名称、实例向服务工厂注册* @param name 服务名称* @param object 服务实例*/void put(String name, Object object);/*** 根据服务名称、类型向服务工厂注册* @param name 服务名称* @param clazz 类型*/void put(String name, Class<?> clazz);/*** 判断是否存在给定的服务名称* @param name 服务名称* @return*/boolean exist(String name);/*** 根据给定的类型查找服务实例* @param clazz 类型* @return*/<T> T find(Class<T> clazz);/*** 根据给定的类型查找所有此类型的服务实例* @param clazz 类型* @return*/<T> List<T> findList(Class<T> clazz);/*** 根据给定的服务名称、类型查找服务实例* @param name 服务名称* @param clazz 类型* @return*/<T> T findByName(String name, Class<T> clazz);
}

服务容器类

常用的形式是DBAccess access = ServiceContext.find(DBAccess.class);

public interface Context {/*** 根据服务名称、实例向服务工厂注册* @param name 服务名称* @param object 服务实例*/void put(String name, Object object);/*** 根据服务名称、类型向服务工厂注册* @param name 服务名称* @param clazz 类型*/void put(String name, Class<?> clazz);/*** 判断是否存在给定的服务名称* @param name 服务名称* @return*/boolean exist(String name);/*** 根据给定的类型查找服务实例* @param clazz 类型* @return*/<T> T find(Class<T> clazz);/*** 根据给定的类型查找所有此类型的服务实例* @param clazz 类型* @return*/<T> List<T> findList(Class<T> clazz);/*** 根据给定的服务名称、类型查找服务实例* @param name 服务名称* @param clazz 类型* @return*/<T> T findByName(String name, Class<T> clazz);
}

Configuration#parser(),解析snaker.xmlbase.config.xml

	protected void parser() {if(log.isDebugEnabled()) {log.debug("Service parsing start......");}//默认使用snaker.xml配置自定义的beanString config = ConfigHelper.getProperty("config");if (StringHelper.isEmpty(config)) {config = USER_CONFIG_FILE;}parser(config);parser(BASE_CONFIG_FILE);if (!isCMB()) {parser(EXT_CONFIG_FILE);for(Entry<String, Class<?>> entry : txClass.entrySet()) {if(interceptor != null) {Object instance = interceptor.getProxy(entry.getValue());ServiceContext.put(entry.getKey(), instance);} else {ServiceContext.put(entry.getKey(), entry.getValue());}}}if(log.isDebugEnabled()) {log.debug("Service parsing finish......");}}

Configuration#parser(java.lang.String),解析xml文件,加载xml配置的class对象到容器中。

	private void parser(String resource) {//解析所有配置节点,并实例化class指定的类DocumentBuilder documentBuilder = XmlHelper.createDocumentBuilder();try {if (documentBuilder != null) {InputStream input = StreamHelper.openStream(resource);if(input == null) return;Document doc = documentBuilder.parse(input);Element configElement = doc.getDocumentElement();NodeList nodeList = configElement.getChildNodes();int nodeSize = nodeList.getLength();for(int i = 0; i < nodeSize; i++) {Node node = nodeList.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {Element element = (Element)node;String name = element.getAttribute("name");String className = element.getAttribute("class");String proxy = element.getAttribute("proxy");if(StringHelper.isEmpty(name)) {name = className;}if(ServiceContext.exist(name)) {log.warn("Duplicate name is:" + name);continue;}Class<?> clazz = ClassHelper.loadClass(className);if(TransactionInterceptor.class.isAssignableFrom(clazz)) {interceptor = (TransactionInterceptor)ClassHelper.instantiate(clazz);ServiceContext.put(name, interceptor);continue;}if(proxy != null && proxy.equalsIgnoreCase("transaction")) {txClass.put(name, clazz);} else {ServiceContext.put(name, clazz);}}}}} catch (Exception e) {e.printStackTrace();throw new SnakerException("资源解析失败,请检查配置文件[" + resource + "]", e.getCause());}}

在这里插入图片描述

相关文章:

【开源项目】snakeflow流程引擎研究

项目地址 https://gitee.com/yuqs/snakerflow https://toscode.mulanos.cn/zc-libre/snakerflow-spring-boot-stater &#xff08;推荐&#xff09; https://github.com/snakerflow-starter/snakerflow-spring-boot-starter 常用API 部署流程 processId engine.process().de…...

11.10 知识总结(数据的增删改查、如何创建表关系、Django框架的请求生命周期流程图)

一、 数据的增删改查 1.1 用户列表的展示 把数据表中得用户数据都给查询出来展示在页面上 添加数据 id username password gender age action 修改 删除 1.2 修…...

AI脑控机器人应用前景如何?

脑控机器人应用前景可谓广阔无边。其轻松的风格不仅使我们能够享受更便捷、更舒适的生活&#xff0c;还为我们带来了无限的可能性。 首先&#xff0c;脑控机器人应用可以在医疗领域发挥重要作用。通过与人类大脑的直接连接&#xff0c;脑控机器人可以为残疾人士提供更高效的康…...

Apache和Nginx实现虚拟主机的3种方式

目录 首先介绍一下Apache和nginx&#xff1a; Nginx和Apache的不同之处&#xff1a; 虚拟主机 准备工作 Apache实现&#xff1a; 方法1&#xff1a;使用不同的ip来实现 方法2&#xff1a;使用相同的ip&#xff0c;不同的端口来实现 方法3&#xff1a;使用相同的ip&…...

【DP】背包问题全解

一.简介 DP&#xff08;动态规划&#xff09;背包问题是一个经典的组合优化问题&#xff0c;通常用来解决资源分配的问题&#xff0c;如货物装载、投资组合优化等。问题的核心思想是在有限的资源约束下&#xff0c;选择一组物品以最大化某种价值指标&#xff0c;通常是总价值或…...

04 jenkins中使用各种变量(Powershell、cmd)

批处理中使用jenkins内部和变量插件定义的环境变量&#xff1a;%WORKSPACE%Powershell插件中使用jenkins内部环境变量&#xff1a;${ENV:WORKSPRACE}Powershell函数内部使用函数入参&#xff1a;$($dllname)Powershell中定义变量&#xff1a;$DllNamePowershell中使用powershel…...

2023年云计算的发展趋势

随着互联网和信息技术的快速发展&#xff0c;云计算已经成为了企业和个人的重要工具&#xff0c;而在未来&#xff0c;云计算仍然会持续发展&#xff0c;并且发展趋势会更加迅猛。在本文中&#xff0c;我们将讨论2023年云计算的发展趋势。 一、混合云将成为主流 混合云是指将公…...

工作十年+的测试应该具备什么能力?

大概是2014年的时候&#xff0c;我开始接触面试工作&#xff0c;就是从应聘者转为面试官&#xff0c;记得印象深刻的是面试了一位做了8年的测试。对方气场很足&#xff0c;嗯&#xff0c;毕竟那时的我还只是一个3、4年经验的小测试&#xff0c;相反&#xff0c;印象深刻的并不是…...

区块链链游合约系统开发项目模式技术方案

​随着区块链技术的发展&#xff0c;链游合约系统开发逐渐成为了一个备受关注的项目。本文将探讨区块链链游合约系统开发项目的技术方案&#xff0c;包括项目背景、开发目标、技术架构、系统流程、安全措施等方面的内容。 一、项目背景 链游是一种基于区块链技术的游戏&#xf…...

业务出海之服务器探秘

这几年随着国内互联网市场的逐渐饱和&#xff0c;越来越多的公司加入到出海的行列&#xff0c;很多领域都取得了很不错的成就。虽然出海可以获得更加广阔的市场&#xff0c;但也需要面对很多之前在国内可能没有重视的一些问题。集中在海外服务器的选择维度上就有很大的变化。例…...

飞天使-django创建一个初始项目过程

创建django项目 运行项目 运行命令 pyhont manage.py runserver 然后访问 http://127.0.0.1:8000/&#xff0c; 则可以打开本地新建的项目 虚拟环境的部署-mac 在一台计算机上可以通过虚拟环境实现多个版本Django的开发环境 安装虚拟环境工具&#xff1a;如果你的系统中没有安…...

【工具插件类教学】全局积雪系统和雪痕迹显示(移动痕迹)

目录 一、演示场景对比效果 ​二、导入工具插件 三、使用流程 1.添加脚本组件GlobalSnow...

​软考-高级-系统架构设计师教程(清华第2版)【第3章 信息系统基础知识(p120~159)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第3章 信息系统基础知识(p120~159)-思维导图】 课本里章节里所有蓝色字体的思维导图...

STM32基础--NVIC中断控制器

一、NVIC是什么&#xff1f; NVIC是一种中断控制器。当一个中断正在处理时&#xff0c;另一个更高优先级的中断可以打断当前中断的执行&#xff0c;并立即得到处理。这种机制使得处理器在高速运行的同时&#xff0c;能够及时响应不同优先级的中断请求。 二、有哪些优先级&…...

使用matlab实现图像信号的色彩空间转换

利用matlab对图像信号进行读取&#xff0c;并对RGB空间进行转换&#xff0c;如转换到HSI空间等。 下面的这个代码是在使用了rgb2hsi()方法失败后&#xff0c;进行修改的。 rgb2hsi(img)这个方法可以将RGB图像转换为HIS图像&#xff1b;但是爆出了 Untitled5(line 5)hsi rgb2h…...

Vatee万腾科技决策力的引领创新:Vatee数字化视野的崭新天地

在数字时代的激烈竞争中&#xff0c;Vatee万腾以其科技决策力的引领&#xff0c;开创了数字化视野的崭新天地。这并不仅仅是一场技术的飞跃&#xff0c;更是一次对未来的深刻洞察和引领创新的勇敢实践。 Vatee万腾的科技决策力不仅仅停留在数据分析和算法的运用&#xff0c;更是…...

Go语言安装教程

【Go系列-1】-Go安装教程 环境提前准备 安装的时候可以选择自己的目录进行环境管理 E:\Z_Enviroment\Go创建文件夹&#xff1a; E:\Z_Enviroment\Go E:\Z_Enviroment\GoWorks E:\Z_Enviroment\GoWorks\bin E:\Z_Enviroment\GoWorks\pkg E:\Z_Enviroment\GoWorks\src环境变量…...

MVVM框架:图片加载有问题

一、前言&#xff1a;在我使用ImageView加载图片的时候添加如下代码发现报错 app:imageUrl"{viewModel.observableField.assetImg}"报错如下错误 二、原因&#xff1a;是啥我不太清楚好像是没有imageView的适配器&#xff0c;后来我看了一下确实没有 public class I…...

一篇文章搞明白js运行机制——事件循环

1、解释 JavaScript 的执行机制。 JavaScript 的执行机制基于事件循环。事件循环包括一个任务队列&#xff08;Task Queue&#xff09;和一个微任务队列&#xff08;Microtask Queue&#xff09;。当一个函数被调用时&#xff0c;它被添加到微任务队列中。事件循环每次迭代都会…...

Leetcode 第 371 场周赛题解

Leetcode 第 371 场周赛题解 Leetcode 第 371 场周赛题解题目1&#xff1a;100120. 找出强数对的最大异或值 I思路代码复杂度分析 题目2&#xff1a;100128. 高访问员工思路代码复杂度分析 题目3&#xff1a;100117. 最大化数组末位元素的最少操作次数思路代码复杂度分析 题目4…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...