業務規則引擎淺析

来源:http://www.cnblogs.com/jietang/archive/2016/03/26/5321841.html
-Advertisement-
Play Games

在CRM(客戶關係管理)系統或者其他業務支撐型系統的開發過程中,最經常多變的就是複雜的業務規則。因為這些規則要迎合、順應市場的變化,如何能有效到做到業務規則和整體的系統支撐架構解耦分離,這個是開發過程中必須考慮的一個問題。每當客戶要求改變一個業務規則的時候,我們又如何能做到在最短的時間內完成需求的開 ...


  在CRM(客戶關係管理)系統或者其他業務支撐型系統的開發過程中,最經常多變的就是複雜的業務規則。因為這些規則要迎合、順應市場的變化,如何能有效到做到業務規則和整體的系統支撐架構解耦分離,這個是開發過程中必須考慮的一個問題。每當客戶要求改變一個業務規則的時候,我們又如何能做到在最短的時間內完成需求的開發提交,提高系統的靈活度?業務規則引擎無非是一個比較好的解決方案。它把複雜、冗餘的業務規則同整個支撐系統分離開,做到架構的可復用移植,這個就是我們的終極目標。

  那規則引擎又是什麼東西?嚴格來說,它是一種嵌入到應用程式中的一個組件,能很好的把業務決策從應用程式框架中分離出來,然後使用預定義的方言(dialect)編寫語義模塊和業務決策模塊,使用約定好的語法規範,接受用戶的輸入,然後解析用戶的業務規則,然後根據解析好的業務規則,作出業務決策。可以說,一個好的支撐系統,離不開一個靈活的業務規則引擎,在某種意義上可以做到“以不變應萬變”。

  言歸正傳,那既然業務規則引擎這麼好?那要如何設計實現呢?寫到這裡,我忽然就想起我大學的時候,我的啟蒙老師跟我說的一句話:我們可以不要重覆發明輪子,但是要能很好的運用和理解如何使用別人造好的“輪子”。這句話一直是我的座右銘。現在就有一個開源的業務規則引擎Drools,就能很好的滿足我們的要求。以此為基礎,站在巨人的肩膀上,何樂而不為呢?

  Drools是什麼?

  簡單來說,Drools是基於Java的規則引擎框架,是JBoss開源社區中的一個為Java量身定製的、基於RETE演算法的產生式規則引擎的實現。大致的工作原理是,基於XML、DRL(Drools規則配置文件)的基礎上,通過一個內置的解析器,把業務規則翻譯成AST(Abstract Syntax Tree),最終會映射編譯成Java的代碼包,然後在程式運行的時候,載入這些代碼包中的業務規則,並把在工作記憶體空間的規則和事實進行匹配,看下事實是否符合業務規則的約定。

  業務規則引擎的架構設計

  主要從兩方面考慮,把常用的業務規則腳本放置到資料庫進行存儲,後續為了節省程式的IO開銷,可以通過緩存機制從資料庫從增量拷貝業務規則的鏡像,程式客戶端直接同緩存打交道。目前基於Java可以考慮的緩存框架也有很多,比如JCS(Java Caching System)。另外一個方面如果一個系統是分散式架構,可以考慮通過Zookeeper上面的節點進行業務規則的分散式部署,也可以實現規則的灰度版本發佈、業務規則的動態事件監控等等操作。同樣的,程式客戶端可以直接同Zookeeper的某個節點通信,獲得業務規則的數據,Zookeeper本身也會自動同步資料庫中的業務規則。綜上所述,得到如下圖所示的業務規則引擎架構

  

  業務規則引擎主要包含如下模塊

  1. BusinessRuleExecutor 規則引擎配置執行器介面:主要用來定義獲取業務規則配置的動作方式。
  2. BusinessRuleExecutorImpl 規則引擎配置載入模塊:實現了BusinessRuleExecutor介面,目前演示的是基於Oracle資料庫,載入業務規則配置的動作方式。
  3. BusinessRule 業務規則元素:定義業務規則的介面,主要包含:業務規則標識、業務規則內容。
  4. BusinessRuleRunner 業務規則引擎執行器:主要是執行具體的業務規則腳本代碼,觸發相應的事件判斷。

  類圖層次結構如下所示

  

  現在我們就用一個實際的例子,配合上面的框架來進行一下講解。

  在移動業務支撐型系統中,為了留住更多的在網移動用戶,業務規定,凡是現有用戶中,有訂購家庭產品和VPN產品,並且他是動感地帶品牌的,都認為是移動的幸運用戶。如果我們遇到這種業務規則,又應該如何來實現?

  我們首先梳理一下業務類圖實體結構:

  我們發現,這裡實際上有三個實體,一個是用戶,一個是用戶訂購的產品,一個是幸運客戶。並且一個用戶是可以訂購多個產品的,用戶的品牌有動感地帶,此外還有全球通。主要針對的產品是VPN產品(VPNPRODUCT)和家庭產品(FAIMILYPROUDCT),於是我們畫出如下的類圖結構:

  

  現在理清楚了業務規則和潛在實體,我們現在來看下如何實現?

  首先在資料庫中,我們可以簡單設計如下表結構進行規則存儲,表結構如下所示(基於Oracle)

  create table pms.ng_business_rule
  (
    rule_id number(4),
    drl_content varchar2(1024)
  );

  其中rule_id表示規則編碼,drl_content表示規則的內容,具體的JavaBean映射結構如下

/**
 * @filename:BusinessRule.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:業務規則定義
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

import java.io.Serializable;

public class BusinessRule implements Serializable {
    private Integer ruleId;
    private String drlContent;

    public Integer getRuleId() {
        return ruleId;
    }

    public void setRuleId(Integer ruleId) {
        this.ruleId = ruleId;
    }

    public String getDrlContent() {
        return drlContent;
    }

    public void setDrlContent(String drlContent) {
        this.drlContent = drlContent;
    }

    @Override
    public String toString() {
        return "BusinessRule{id:" + getRuleId() + "|rule:" + getDrlContent()
                + "}";
    }
}

  現在再來看下,規則引擎配置執行器介面定義的內容

/**
 * @filename:BusinessRuleExecutor.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:規則引擎配置執行器介面定義
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

import java.util.List;

public interface BusinessRuleExecutor {
    List<BusinessRule> findAll();
    List<BusinessRule> findAllByRuleId(Integer ruleId);
}

  然後是基於Oracle資料庫載入業務規則配置模塊,當然你還可以繼續實現BusinessRuleExecutor介面的方法,完成緩存讀取、Zookeeper方式讀取業務規則的相應模塊,這裡就不再覆述實現細節,後續有時間可以補上。

/**
 * @filename:BusinessRuleExecutorImpl.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:規則引擎配置載入模塊(DB方式)
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;

import java.util.List;
import java.util.Map;

public class BusinessRuleExecutorImpl extends NamedParameterJdbcDaoSupport
        implements BusinessRuleExecutor {

    private static final RowMapper<BusinessRule> ruleMapper = ParameterizedBeanPropertyRowMapper.newInstance(BusinessRule.class);

    private Map<String, String> ruleList;

    protected String getRuleList(String key) {
        return (ruleList != null) ? ruleList.get(key) : null;
    }

    public void setRuleList(Map<String, String> ruleList) {
        this.ruleList = ruleList;
    }

    protected <T> List<T> query(String queryId, RowMapper<T> rowMapper, Object... args) {
        return getJdbcTemplate().query(getRuleList(queryId), rowMapper, args);
    }

    public List<BusinessRule> findAll() {
        return query("select-rule", ruleMapper);
    }

    public List<BusinessRule> findAllByRuleId(Integer ruleId) {
        return query("select-rule-by-id", ruleMapper, ruleId);
    }
}

  接下來就是關鍵的模塊,業務規則引擎執行器,是基於Drools的實現,drlContent是指業務規則的內容,elements是指業務規則中關註的事實對象。具體代碼如下

/**
 * @filename:BusinessRuleRunner.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:業務規則引擎執行器
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;

import org.drools.builder.ResourceType;
import org.drools.definition.KnowledgePackage;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;

public class BusinessRuleRunner {

    public BusinessRuleRunner() {}
    
    public void notify(String drlContent, ArrayList<Object> elements) {

        //構建知識庫引擎
        KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
        
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

        try {
            kbuilder.add(ResourceFactory.newInputStreamResource(getDrlStream(drlContent)), ResourceType.DRL);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
        kbase.addKnowledgePackages(pkgs);
        
        StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
        
        //drl腳本有編譯問題要提示
        if (kbuilder.hasErrors()) {
            System.out.println(kbuilder.getErrors().toString());
            throw new RuntimeException("Unable to compile: " + drlContent+ "\n");
        }
        
        //插入WorkingMemory
        for (int i = 0; i < elements.size(); i++) {
            Object fact = elements.get(i);
            ksession.insert(fact);
        }

        //激活規則
        ksession.fireAllRules();
    }
    
    private InputStream getDrlStream(String drlContent) throws Exception{  
        ByteArrayInputStream is = new ByteArrayInputStream(drlContent.getBytes());  
        return is;  
    }  
}

  然後用Spring框架實現業務規則配置的自動裝配,首先是business-rule-config-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="newlandframework/ruleengine/db.properties" />
    </bean>
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>
    <bean id="ruleengine-config" class="newlandframework.ruleengine.BusinessRuleExecutorImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
        <property name="ruleList">
            <map>
                <entry key="select-rule">
                    <value><![CDATA[
              select rule_id,drl_content from ng_business_rule
            ]]></value>
                </entry>
                <entry key="select-rule-by-id">
                    <value><![CDATA[
              select rule_id,drl_content from ng_business_rule where rule_id = ?
            ]]></value>
                </entry>
            </map>
        </property>
    </bean>
</beans>

  資料庫方式實現業務規則配置讀取的事務控制Spring配置:business-rule-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<import resource="business-rule-config-spring.xml" /> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="servicePointcut" expression="execution(* newlandframework.ruleengine.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/> </aop:config> </beans>

  再看下用戶、用戶產品、幸運用戶的實體類定義

package newlandframework.ruleengine;

/**
 * @filename:Users.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:用戶定義
 * @author tangjie
 * @version 1.0
 * 
 */

import java.util.List;

public class Users {
    // 全球通品牌
    public static final Integer GOTONE = 1000;
    // 動感地帶品牌
    public static final Integer MZONE = 1016;

    // 用戶歸屬地市編碼(591表示福州/592表示廈門)
    private Integer homeCity;
    // 用戶的手機號碼
    private Integer msisdn;
    // 用戶標識
    private Integer userId;
    // 用戶品牌標識
    private Integer userBrand;

    private List<UserProduct> userProduct;

    public List<UserProduct> getUserProduct() {
        return userProduct;
    }

    public void setUserProduct(List<UserProduct> userProduct) {
        this.userProduct = userProduct;
    }

    public Integer getHomeCity() {
        return homeCity;
    }

    public void setHomeCity(Integer homeCity) {
        this.homeCity = homeCity;
    }

    public Integer getMsisdn() {
        return msisdn;
    }

    public void setMsisdn(Integer msisdn) {
        this.msisdn = msisdn;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getUserBrand() {
        return userBrand;
    }

    public void setUserBrand(Integer userBrand) {
        this.userBrand = userBrand;
    }

    @Override
    public String toString() {
        return "Users [homeCity=" + homeCity + ", msisdn=" + msisdn
                + ", userId=" + userId + ", userBrand=" + userBrand
                + ", userProduct=" + userProduct + "]";
    }
}


/**
 * @filename:UserProduct.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:用戶產品定義
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

public class UserProduct {
    // VPN產品編碼
    public static final Integer VPNPRODUCT = 1000000001;
    // 家庭產品編碼
    public static final Integer FAIMILYPROUDCT = 1000000002;

    // 用戶歸屬地市編碼(591表示福州/592表示廈門)
    private Integer homeCity;
    // 用戶標識
    private Integer userId;
    // 產品編碼
    private Integer productId;
    // 產品名稱描述
    private String productName;

    public Integer getHomeCity() {
        return homeCity;
    }

    public void setHomeCity(Integer homeCity) {
        this.homeCity = homeCity;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getProductId() {
        return productId;
    }

    public void setProductId(Integer productId) {
        this.productId = productId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    @Override
    public String toString() {
        return "UserProduct [homeCity=" + homeCity + ", userId=" + userId
                + ", productId=" + productId + ", productName=" + productName
                + "]";
    }
}


/**
 * @filename:LuckUsers.java
 *
 * Newland Co. Ltd. All rights reserved.
 * 
 * @Description:幸運用戶定義
 * @author tangjie
 * @version 1.0
 * 
 */

package newlandframework.ruleengine;

public class LuckUsers {
    // 用戶歸屬地市編碼(591表示福州/592表示廈門)
    private Integer homeCity;
    // 用戶的手機號碼
    private Integer msisdn;
    // 用戶標識
    private Integer userId;

    public LuckUsers() {

    }

    public LuckUsers(Integer homeCity, Integer msisdn, Integer userId) {
        super();
        this.homeCity = homeCity;
        this.msisdn = msisdn;
        this.userId = userId;
    }

    public Integer getHomeCity() {
        return homeCity;
    }

    public void setHomeCity(Integer homeCity) {
        this.homeCity = homeCity;
    }

    public Integer getMsisdn() {
        return msisdn;
    }

    public void setMsisdn(Integer msisdn) {
        this.msisdn = msisdn;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "LuckUsers [homeCity=" + homeCity + ", msisdn=" + msisdn
                + ", userId=" + userId + "]";
    }
}
  最後是Drools的業務規則配置的內容,我們把它入庫到資料庫表pms.ng_business_rule中的drl_content欄位,其中規則標識可以用序列自動生成。

  業務規則的主要邏輯就是判斷這個用戶是不是幸運用戶?業務判斷標準是:該用戶訂購了家庭產品和VPN產品,並且他的品牌是動感地帶。

//created on: 2016-3-26
//業務規則決策配置 by tangjie
package newlandframework.ruleengine //list any import classes here. import newlandframework.ruleengine.UserProduct; import newlandframework.ruleengine.Users; import newlandframework.ruleengine.LuckUsers; //declare any global variables here rule "genLuckyUsersRule" dialect "mvel" when $luck : LuckUsers() $userFamilyProduct : UserProduct( productId == UserProduct.FAIMILYPROUDCT ) $userVpnProduct : UserProduct( productId == UserProduct.VPNPRODUCT ) $user : Users(userProduct contains $userFamilyProduct) and Users(userProduct contains $userVpnProduct) eval($user.userBrand == Users.MZONE) then //actions System.out.println("family:"+$userFamilyProduct.productId); System.out.println("vpn:"+$userVpnProduct.productId); System.out.println("msisdn:"+$user.msisdn); $luck.homeCity = $user.homeCity; $luck.msisdn = $user.msisdn; $luck.userId = $user.userId; end

  最後我們可以在客戶端中初始化一個屬性(homeCity、msisdn、userId)都是空(null)的幸運用戶對象LuckyUsers,然後傳入一個用戶剛好符合上述業務規則決策條件的“事實”用戶,調用方式參考代碼如下:

 

//創建一個預設的幸運用戶對象
LuckUsers luck = new LuckUsers();

//業務規定的vpn產品對象
UserProduct vpn = new UserProduct();
vpn.setProductId(UserProduct.VPNPRODUCT);

//業務規定的家庭產品對象
UserProduct family = new UserProduct();
family.setProductId(UserProduct.FAIMILYPROUDCT);

//存放用戶已經訂購的產品列表
List<UserProduct> listProduct = new ArrayList<UserProduct>();

//創建測試用戶,用戶號碼119,用戶歸屬地市591,用戶標識1240
Integer homeCity = new Integer(591);
Integer userId = new Integer(1240);
Integer msisdn = new Integer(119);
//假設用戶還訂購了其他的4G飛享套餐,產品編碼是1000000003
Integer otherProductId = new Integer(1000000003);

UserProduct userProduct1 = new UserProduct();
userProduct1.setHomeCity(homeCity);
userProduct1.setProductId(otherProductId);
userProduct1.setProductName("4G飛享套餐");
userProduct1.setUserId(userId);
listProduct.add(userProduct1);

UserProduct userProduct2 = new UserProduct();
userProduct2.setHomeCity(homeCity);
userProduct2.setProductId(UserProduct.VPNPRODUCT);
userProduct2.setProductName("VPN產品");
userProduct2.setUserId(userId);
listProduct.add(userProduct2);

UserProduct userProduct3 = new UserProduct();
userProduct3.setHomeCity(homeCity);
userProduct3.setProductId(UserProduct.FAIMILYPROUDCT);
userProduct3.setProductName("家庭產品");
userProduct3.setUserId(userId);
listProduct.add(userProduct3);

Users user = new Users();
user.setHomeCity(homeCity);
user.setMsisdn(msisdn);
user.setUserBrand(1016);
user.setUserId(userId);
user.setUserProduct(listProduct);

//業務規則關註的事實對象
ArrayList<Object> elements = new ArrayList<Object>();
elements.add(vpn);
elements.add(family);
elements.add(userProduct1);
elements.add(userProduct2);
elements.add(userProduct3);
elements.add(user);
elements.add(luck);

//加入業務規則引擎中執行決策
new BusinessRuleRunner().notify(drlContent, elements);

 

  好了,我們執行一下代碼,看下運行結果:

  可以很清楚的看到,符合條件的用戶,果然被我們找到,並且列印出來了。就是上面homeCity = 591,msisdn = 119,userId=1240的記錄。

  並且後續如果業務部門又改變業務規則,我們只要重新編寫或者修改一個規則配置,然後重新發佈、刷新緩存,既可以符合要求,又省去了很多代碼編譯、發佈、上線等等一系列繁瑣的中間步驟。最關鍵的是我們的代碼框架也會變得非常靈活。

  本文的內容是基於本人日常開發工作中應對複雜多變的業務規則的一種解決方式的設想,當然其中肯定有很多需要完善優化的地方,希望在此拋磚引玉,不吝賜教!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define N 15 5 6 int chessboard[N + 1][N + 1] = { 0 }; 7 8 int whoseTurn = 0; 9 10 void initGame(void); ...
  • 1. 創建一個context processor函數 新建一個文件命名為custom_processors.py,把它放到項目app文件夾(例如我的blog文件夾),添加一個返回字典的函數,其代碼如下: from sets import Set from django.db.models impor ...
  • 從今天起, 我會每天把閱讀tiny_cnn的閱讀心得提交到博客園中希望大家在這個平臺上可以多多交流; 關於如果閱讀代碼? 抓住重點,忽略細節 首先打開從github上下載的文件: 通過csdn和網上搜索一番會知道這個文件的各個目錄存放的是什麼; 我用${root} 代表到tiny-cnn-maste... ...
  • 命名空間提供了一種從邏輯上組織類的方式,防止命名衝突。 幾種常見語言 C++ 命名空間是可以嵌套的 嵌套的命名空間是指定義在其他命名空間中的命名空間。嵌套的命名空間是一個嵌套的作用域,內層命名空間聲明的名字將隱藏外層命名空間聲明的同名成員: C++ int x = 20; namespace out ...
  • 在前面幾次討論中我們介紹了Free是個產生Monad的最基本結構。它的原理是把一段程式(AST)一連串的運算指令(ADT)轉化成數據結構存放在記憶體里,這個過程是個獨立的功能描述過程。然後另一個獨立運算過程的Interpreter會遍歷(traverse)AST結構,讀取結構里的運算指令,實際運行指令 ...
  • 1 #-*- coding:utf-8 -*- 2 3 import curses 4 from random import randrange, choice # generate and place new tile 5 from collections import defaultdict 6 ...
  • 題目: 計算字元串最後一個單詞的長度,單詞以空格隔開。 輸入: 一行字元串。 輸出: 整數N,最後一個單詞的長度。 例如: hello world 輸出: 5 思路: 考慮到最後一個單詞中可能含有空格,例如: “Hello world#" (#表示空格) int lenOfLastWord(cons... ...
  • 用PHPExcel做導出execl的時候發現在本地沒有問題,但是把網站傳到租用的伺服器的時候就報錯,具體如下: 代碼如下: ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...