Activiti工作流學習-----基於5.19.0版本(4)

来源:http://www.cnblogs.com/liujie037/archive/2016/08/21/5790698.html
-Advertisement-
Play Games

四、使用工作流開發 org.activiti.engine.ProcessEngine提供的Service作用在工作流引擎上面,如果所示是模仿一個公司簡單的審批流程,你可以下載這個Demo:Activiti unit test template玩玩。 發佈這個流程圖可以通過RepositorySer ...


四、使用工作流開發

 org.activiti.engine.ProcessEngine提供的Service作用在工作流引擎上面,如果所示是模仿一個公司簡單的審批流程,你可以下載這個Demo:Activiti unit test template玩玩。

發佈這個流程圖可以通過RepositoryService進行,在資料庫中存儲的這些靜態數據是這些:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://activiti.org/bpmn20" id="definitions">
  <process id="vacationRequest" name="Vacation request" isExecutable="true">
    <startEvent id="request" activiti:initiator="employeeName">
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" type="long" required="true"></activiti:formProperty>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" type="date" datePattern="dd-MM-yyyy hh:mm" required="true"></activiti:formProperty>
        <activiti:formProperty id="vacationMotivation" name="Motivation" type="string"></activiti:formProperty>
      </extensionElements>
    </startEvent>
    <sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest"></sequenceFlow>
    <userTask id="handleRequest" name="處理休假單" activiti:candidateGroups="management">
      <documentation>${employeeName} would like to take ${numberOfDays} day(s) of vacation (Motivation: ${vacationMotivation}).</documentation>
      <extensionElements>
        <activiti:formProperty id="vacationApproved" name="Do you approve this vacation" type="enum" required="true">
          <activiti:value id="true" name="Approve"></activiti:value>
          <activiti:value id="false" name="Reject"></activiti:value>
        </activiti:formProperty>
        <activiti:formProperty id="managerMotivation" name="Motivation" type="string"></activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision"></sequenceFlow>
    <exclusiveGateway id="requestApprovedDecision" name="Request approved?"></exclusiveGateway>
    <sequenceFlow id="flow3" name="同意" sourceRef="requestApprovedDecision" targetRef="sendApprovalMail">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${vacationApproved == 'true'}]]></conditionExpression>
    </sequenceFlow>
    <manualTask id="sendApprovalMail" name="發送郵件"></manualTask>
    <sequenceFlow id="flow4" sourceRef="sendApprovalMail" targetRef="theEnd1"></sequenceFlow>
    <endEvent id="theEnd1"></endEvent>
    <sequenceFlow id="flow5" name="不同意" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${vacationApproved == 'false'}]]></conditionExpression>
    </sequenceFlow>
    <userTask id="adjustVacationRequestTask" name="修改休假單" activiti:assignee="${employeeName}">
      <documentation>Your manager has disapproved your vacation request for ${numberOfDays} days.
        Reason: ${managerMotivation}</documentation>
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" type="long" required="true"></activiti:formProperty>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" type="date" datePattern="dd-MM-yyyy hh:mm" required="true"></activiti:formProperty>
        <activiti:formProperty id="vacationMotivation" name="Motivation" type="string"></activiti:formProperty>
        <activiti:formProperty id="resendRequest" name="Resend vacation request to manager?" type="enum" required="true">
          <activiti:value id="true" name="Yes"></activiti:value>
          <activiti:value id="false" name="No"></activiti:value>
        </activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow6" sourceRef="adjustVacationRequestTask" targetRef="resendRequestDecision"></sequenceFlow>
    <exclusiveGateway id="resendRequestDecision" name="Resend request?"></exclusiveGateway>
    <sequenceFlow id="flow7" name="重新請求處理" sourceRef="resendRequestDecision" targetRef="handleRequest">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${resendRequest == 'true'}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow8" name="放棄休假" sourceRef="resendRequestDecision" targetRef="theEnd2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${resendRequest == 'false'}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="theEnd2"></endEvent>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_vacationRequest">
    <bpmndi:BPMNPlane bpmnElement="vacationRequest" id="BPMNPlane_vacationRequest">
      <bpmndi:BPMNShape bpmnElement="request" id="BPMNShape_request">
        <omgdc:Bounds height="35.0" width="35.0" x="1.0" y="61.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="handleRequest" id="BPMNShape_handleRequest">
        <omgdc:Bounds height="60.0" width="100.0" x="102.0" y="49.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="requestApprovedDecision" id="BPMNShape_requestApprovedDecision">
        <omgdc:Bounds height="40.0" width="40.0" x="237.0" y="58.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="sendApprovalMail" id="BPMNShape_sendApprovalMail">
        <omgdc:Bounds height="60.0" width="100.0" x="391.0" y="49.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="theEnd1" id="BPMNShape_theEnd1">
        <omgdc:Bounds height="35.0" width="35.0" x="641.0" y="61.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="adjustVacationRequestTask" id="BPMNShape_adjustVacationRequestTask">
        <omgdc:Bounds height="60.0" width="100.0" x="391.0" y="165.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="resendRequestDecision" id="BPMNShape_resendRequestDecision">
        <omgdc:Bounds height="40.0" width="40.0" x="541.0" y="174.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="theEnd2" id="BPMNShape_theEnd2">
        <omgdc:Bounds height="35.0" width="35.0" x="641.0" y="177.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="36.0" y="78.0"></omgdi:waypoint>
        <omgdi:waypoint x="102.0" y="79.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="202.0" y="79.0"></omgdi:waypoint>
        <omgdi:waypoint x="237.0" y="78.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="277.0" y="78.0"></omgdi:waypoint>
        <omgdi:waypoint x="320.0" y="77.0"></omgdi:waypoint>
        <omgdi:waypoint x="391.0" y="79.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="277.0" y="78.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="491.0" y="79.0"></omgdi:waypoint>
        <omgdi:waypoint x="523.0" y="78.0"></omgdi:waypoint>
        <omgdi:waypoint x="641.0" y="78.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="257.0" y="98.0"></omgdi:waypoint>
        <omgdi:waypoint x="257.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="303.0" y="190.0"></omgdi:waypoint>
        <omgdi:waypoint x="391.0" y="195.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="261.0" y="151.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="491.0" y="195.0"></omgdi:waypoint>
        <omgdi:waypoint x="541.0" y="194.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
        <omgdi:waypoint x="561.0" y="214.0"></omgdi:waypoint>
        <omgdi:waypoint x="561.0" y="329.0"></omgdi:waypoint>
        <omgdi:waypoint x="149.0" y="329.0"></omgdi:waypoint>
        <omgdi:waypoint x="152.0" y="109.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="321.0" y="309.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
        <omgdi:waypoint x="581.0" y="194.0"></omgdi:waypoint>
        <omgdi:waypoint x="641.0" y="194.0"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="14.0" width="100.0" x="581.0" y="194.0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

工作流引擎將會將xml轉化成可執行的java對象和資料庫記錄,即使重啟,工作流引擎仍然知道這些數據。發佈可以這樣書寫:

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
//載入xml流程定義文件 repositoryService.createDeployment() .addClasspathResource(
"org/activiti/test/VacationRequest.bpmn20.xml") .deploy(); Log.info("Number of process definitions: " + repositoryService.createProcessDefinitionQuery().count());

4.1 啟動流程實例

 在成功發佈流程定義到工作流引擎後,我們可以啟動一個新的流程實例。流程定義和流程實例是一對多的關係,是一靜一動的關係。使用RuntimeService可以操作相關的流程,有很多方式啟動流程實例,在下麵的代碼中,我們使用了在流程定義的key來啟動它,同時我們在啟動過程中也添加了流程變數在流程實例中,流程變數大到整個流程實例的作用範圍,小到局部任務節點,流程實例具有的流程變數也是和其他流程變數之間區分的差別。流程變數是一個典型的Map結構:

 1 Map<String, Object> variables = new HashMap<String, Object>();
 2 variables.put("employeeName", "Kermit");
 3 variables.put("numberOfDays", new Integer(4));
 4 variables.put("vacationMotivation", "I'm really tired!");
 5 
 6 RuntimeService runtimeService = processEngine.getRuntimeService();
  //
vacationRequest是開發者在流程定義xml中事先定義好的。

7 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
 8 // Verify that we started a new process instance 
9 Log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());

4.2 完成任務

在流程實例成功啟動後,流程第一步會是一個用戶的任務,他必須由用戶手動完成,而任務的獲取可以使用以下代碼實現:

// 查詢一個叫management的用戶組的組任務
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
  Log.info("Task available: " + task.getName());
}

在查詢出了任務後,流程實例需要繼續開展往往需要我們完成這些任務,完成任務的代碼:

1 Task task = tasks.get(0);
2 
3 Map<String, Object> taskVariables = new HashMap<String, Object>();
4 taskVariables.put("vacationApproved", "false");
5 taskVariables.put("managerMotivation", "We have a tight deadline!");
6 //調用complete方法完成任務
7 taskService.complete(task.getId(), taskVariables);

流程實例將會進入下一步,在例子代碼中,我們將流程變數vacationApproved置為了false,也就是說審批不同意,根據流程圖下一步是返回給請假者,請假者自己處理審批結果,

請假者可以重新提交修改的請假單,流程將會迴圈進入流程圖中的開始任務處。

4.3 流程暫停和激活

 流程定義在流轉中很可能被暫停,如果是流程定義被暫停了,那麼流程實例將不能夠被創建,同時工作流引擎將會拋出異常。暫停流程定義可以這樣實現:

1 repositoryService.suspendProcessDefinitionByKey("vacationRequest");
2 try {
3   runtimeService.startProcessInstanceByKey("vacationRequest");
4 } catch (ActivitiException e) {
5   e.printStackTrace();
6 }

 

為了重新激活流程定義,調用方法repositoryService.activateProcessDefinitionXXX即可。

也有可能流程實例被暫停了,在暫停的時候,流程不能繼續開展(比如在完成任務拋出異常),沒有作業會執行,暫停一個流程實例調用RuntimeService的runtimeService.suspendProcessInstance方法,激活

流程實例執行runtimeService.activateProcessInstanceXXX方法即可。有深入瞭解的同學可以去看我後期的博客,同時Activiti作為一個開源項目,大家也可以直接深入源碼進行學習和查看官方的api文檔。

 4.4 工作流的查詢

 在工作流引擎中查詢有兩種方式:工作流的api查詢和mybatis的sql查詢,activiti的查詢API設計非常優雅,你可以連續的加上不同的限制條件和排序條件(它們在邏輯上都是And形式),例如下麵這段代碼: 

1 List<Task> tasks = taskService.createTaskQuery()
2     .taskAssignee("kermit")
3     .processVariableValueEquals("orderId", "0815")
4     .orderByDueDate().asc()
5     .list();

有時候你需要更加強大的查詢,比如OR條件查詢以及其他無法使用API進行描述的查詢。對於這些情況,activiti建議你使用原生的sql查詢,查詢對象(比如TaskQuery)已經定義了返回不同的對象,比如Task, ProcessInstance, Execution等,使用原生sql查詢需要SQL知識和Activiti表結構知識(比如查詢你至少知道表名是什麼吧),activiti幫我們做了很多,比如下麵代碼:

//拼裝sql語句
List<Task> tasks = taskService.createNativeTaskQuery()
  .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
  .parameter("taskName", "gonzoTask")
  .list();

long count = taskService.createNativeTaskQuery()
  .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
    + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
  .count();

4.5 工作流中的變數

每一個流程實例在流程步驟中需要和使用變數。變數也會存儲在資料庫中,變數也能在表達式中使用(比如排他網關根據流程變數的取值決定流程的走向),在工作流之外其他的service提供服務調用後存儲輸入和輸入結果。

一個流程實例擁有的變數叫做流程變數,執行對象也能擁有變數,不過變數只有當前任務才能擁有,流程繼續執行時就無法再次獲取和存儲上一次執行對象的變數。原則上流程變數數量是沒有限制的,每一個變數都存儲在表ACT_RU_VARIABLE中。

所有的startProcessInstanceXXX方法都提供了變數傳參的方法,比如:

	ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);

 變數也可以在流程執行對象處添加,比如RuntimeService的API:

1 void setVariable(String executionId, String variableName, Object value);
2 void setVariableLocal(String executionId, String variableName, Object value);
3 void setVariables(String executionId, Map<String, ? extends Object> variables);
4 void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

在流程執行對象中設置的變數是局部變數(記住流程實例是由多個樹形結構的執行對象組成),局部變數僅僅是在對應的執行對象中可見,在我們不想讓變數傳播影響到流程實例這一層次的話,可以考慮使用局部變數,又比如在並行網關那裡需要對一個變數賦予新值而不會影響另外其他流程執行的路徑時也會考慮使用局部變數。

在任務對象上對局部變數的存取的API如下所示:(調用TaskService)

1 Map<String, Object> getVariables(String executionId);
2 Map<String, Object> getVariablesLocal(String executionId);
3 Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
4 Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
5 Object getVariable(String executionId, String variableName);
6 <T> T getVariable(String executionId, String variableName, Class<T> variableClass);

變數經常被用在短語、表達式、執行對象或者任務,任務監聽器、腳本等,譬如execution(執行對象)的API:

1 execution.getVariables();
2 execution.getVariables(Collection<String> variableNames);
3 execution.getVariable(String variableName);
4 
5 execution.setVariables(Map<String, object> variables);
6 execution.setVariable(String variableName, Object value);

 由於歷史版本原因,在執行任何上述方法的時候,activiti預設是將所有的變數從資料庫取出來,意味著資料庫存有10個變數,現在你需要取出名叫myVariable的變數,但是其餘的9個也會被取出來並且緩存起來。這並不是很差,因為後期你取變數就不會再從資料庫取出,當然如果你有大量的變數或者在查詢方面你想進一步控制資料庫,這時全部取出就不怎麼合適了。從Activiti5.17版本開始,新添加了的方法支持是否全部查詢:

1 Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
2 Object getVariable(String variableName, boolean fetchAllVariables);
3 void setVariable(String variableName, Object value, boolean fetchAllVariables);

4.6 表達式

 目前工作流使用的表達式語言是UEL,所謂的UEL就是Unified Expression Language,他是javaee6的規範之一,為了讓最新的UEL的所有特性都用在activiti環境中,我們使用了JUEL的修改版。

表達式有兩種:值表達式(value-expression)和方法表達式(method-expression), 在表達式需要的地方,兩種表達式都可以使用。

  • Value expression: 用法和Spring環境相同,所有可以從Spring中讀取值。
${myVar}
${myBean.myProperty}
  • method-expression:執行指定的方法,參數可有可無。activiti依靠圓括弧()區別表達式是method-expression,例如:
${printer.print()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}

 表達式支持對象是beans, lists, arrays and maps.

在流程變數中,activiti已經定義了下麵變數名,它們已經被使用:

  • execution:它含有當前正在執行的執行對象的信息。
  • task:它含有當前任務的信息。
  • authenticatedUserId:當前被驗證的用戶ID,如果沒有用戶被驗證,當前值為空。

4.7 工作流的單元測試與技巧

業務流程是軟體工程中不可或缺的一部分,其中的邏輯應該被測試到。自從activiti能夠嵌入Java應用中後,為業務流程書寫單元測試變得平常簡單了。Activiti支持Junit3和Junit4的測試風格,在junit3中,org.activiti.engine.test.ActivitiTestCase需要被繼承。ActivitiTestCase中protected的方法能夠創建流程引擎和相關的Services,預設的,創建的流程引擎會從類路徑下麵載入activiti.cfg.xml,為了更加靈活的載入,你需要重寫方法:getConfigurationResource()。在多個測試單元測試中如果讀取的配置文件相同,流程引擎會被緩存起來。通過繼承,你可以使用註解@Deployment在方法上面,在執行單元測試方法前,會將該測試類同一目錄的testClassName.testMethod.bpmn20.xml文件載入和發佈,在測試方法結束時候將會刪除流程實例,任務等,而且@Deployment也支持自定義載入文件,可以查看源代碼分析,這裡就不贅述了。

 1 public class MyBusinessProcessTest extends ActivitiTestCase {
 2 
 3   @Deployment
 4   public void testSimpleProcess() {
 5     runtimeService.startProcessInstanceByKey("simpleProcess");
 6
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文網址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 語言編寫的TCP/UDP通信框架 作者是英國人 以前是收費的 目前作者已經開源 開源地址是:https://github.com/MarcFletcher/NetworkComms.Net 使 ...
  • 開始使用 LINQ(五)- LINQ 中的查詢語法和方法語法 在表示語言集成查詢 (LINQ) 使用 LINQ 性查詢語法,文檔中的多數查詢編寫。但是,編譯代碼時,必須將查詢語法轉換為方法,這就需要 .NET 公共語言運行時 (CLR)。這些方法調用標準查詢運算符的名稱類似 Where、Select ...
  • 開始使用 LINQ(四)- LINQ 查詢操作的類型關係 LINQ 查詢操作在數據源、查詢本身及查詢執行中是強類型的。查詢中變數的類型必須與數據源中元素的類型和 foreach 語句中迭代變數的類型相容。此強類型保證在編譯時捕獲類型錯誤,以便可以在用戶遇到這些錯誤之前更正它們。 一、不轉換源數據的查 ...
  • 【簡訊貓相關問題】 【getDeviceNameByRFID】的引用。註意 一系列配置 【簡訊貓SmsHelper】 【簡訊貓DuanXinMao】 ...
  • MXS&Vincene ─╄OvЁ &0000027─╄OvЁ MXS&Vincene MXS&Vincene ─╄OvЁ:今天很殘酷,明天更殘酷,後天很美好,但是絕大部分人是死在明天晚上,只有那些真正的英雄才能見到後天的太陽。 MXS&Vincene ─╄OvЁ:We're here to put ...
  • 索引 【無私分享:從入門到精通ASP.NET MVC】從0開始,一起搭框架、做項目 目錄索引 前言 前面還沒有下載到UI和資料庫的,這裡再次給大家提供一下:百度網盤 提取碼:fuuv ,UI是參照H+,但是H+是收費授權的(¥998RMB),價格有點貴,所以 我們的 UI 跟H+ 雖然是一個風格,但 ...
  • 問題引出: winform程式中的耗時操作,一般不能在UI線程中執行,需要另開線程。往往我們需要在耗時操作結束後將結果顯示在UI上。 以下是Mainform.cs中調用耗時操作的一段代碼: 這裡耗時操作被封裝在類Job中,調用 j.runJob() 開始耗時操作。其中runJob中封裝了開啟新線程執 ...
  • 本篇文章就要根據源碼分析 SparkContext 所做的一些事情,用過Spark的開發者都知道SparkContext是編寫Spark程式用到的第一個類,足以說明SparkContext的重要性;這裡先摘抄SparkContext源碼註釋來 簡單介紹介紹SparkContext,註釋的第一句話就是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...