springboot學習筆記:10.springboot+atomikos+mysql+mybatis+druid+分散式事務

来源:https://www.cnblogs.com/zhaojiatao/archive/2018/02/02/8407276.html
-Advertisement-
Play Games

springboot+atomikos+mysql+mybatis+druid+分散式事務 ...


前言

上一篇文章我們整合了springboot+druid+mybatis+mysql+多數據源;

本篇文章大家主要跟隨你們濤兄在上一屆基礎上配置一下多數據源情況下的分散式事務;

首先,到底啥是分散式事務呢,比如我們在執行一個業務邏輯的時候有兩步分別操作A數據源和B數據源,當我們在A數據源執行數據更改後,在B數據源執行時出現運行時異常,那麼我們必須要讓B數據源的操作回滾,並回滾對A數據源的操作;這種情況在支付業務時常常出現;比如買票業務在最後支付失敗,那之前的操作必須全部回滾,如果之前的操作分佈在多個數據源中,那麼這就是典型的分散式事務回滾;(分散式事務詳解參考)

瞭解了什麼是分散式事務,那分散式事務在java的解決方案就是JTA(即Java Transaction API);springboot官方提供了 Atomikos or Bitronix解決思路

其實,大多數情況下很多公司是使用消息隊列的方式實現分散式事務,這個我們後面的文章會單獨寫一篇使用MQ的方式實現分散式事務;

本篇文章重點講解springboot環境下,整合 Atomikos +mysql+mybatis+tomcat/jetty;

一、項目依賴

pom.xml中添加atomikos的springboot相關依賴:

<!--分散式事務-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

 

點進去會發現裡面整合好了:transactions-jms、transactions-jta、transactions-jdbc、javax.transaction-api

二、把數據源的相關配置項單獨提煉到一個application.yml中:

註意:

1.這回我們的spring.datasource.type 是com.alibaba.druid.pool.xa.DruidXADataSource;

2.spring.jta.transaction-manager-id的值在你的電腦中是唯一的,這個詳細請閱讀官方文檔;

 

3.要把之前在application-dev.properties中的spring.datasource.*所有相關配置註釋掉;

完整的yml文件如下:

spring:
  datasource:
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    druid:
    
      systemDB:
        name: systemDB
        url: jdbc:mysql://localhost:3306/springboot-mybatis?useUnicode=true&characterEncoding=utf-8
        username: root
        password: root
        # 下麵為連接池的補充設置,應用到上面所有數據源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一個連接在池中最小生存的時間,單位是毫秒
        minEvictableIdleTimeMillis: 30
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache,並且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合併多個DruidDataSource的監控數據
        useGlobalDataSourceStat: true

      businessDB:
        name: businessDB

        url: jdbc:mysql://localhost:3306/springboot-mybatis2?useUnicode=true&characterEncoding=utf-8
        username: root
        password: root
        # 下麵為連接池的補充設置,應用到上面所有數據源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一個連接在池中最小生存的時間,單位是毫秒
        minEvictableIdleTimeMillis: 30
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache,並且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合併多個DruidDataSource的監控數據
        useGlobalDataSourceStat: true

  #jta相關參數配置
  jta:
    log-dir: classpath:tx-logs
    transaction-manager-id: txManager

 

 

三、在DruidConfig.java中實現多個數據源的註冊;分散式事務管理器的註冊;druid的註冊;

package com.zjt.config;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties;

/**
 * Druid配置
 *
 * @author zhaojiatao
 */
@Configuration
public class DruidConfig {
    @Bean(name = "systemDataSource")
    @Primary
    @Autowired
    public DataSource systemDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.systemDB.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("systemDB");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;

    }

    @Autowired
    @Bean(name = "businessDataSource")
    public AtomikosDataSourceBean businessDataSource(Environment env) {

        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.businessDB.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("businessDB");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);

        return ds;
    }


    /**
     * 註入事物管理器
     * @return
     */
    @Bean(name = "xatx")
    public JtaTransactionManager regTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }


    private Properties build(Environment env, String prefix) {

        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
        prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
        prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
        prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));

        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));

        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
        prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
        prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
        prop.put("filters", env.getProperty(prefix + "filters"));

        return prop;
    }










    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        //控制台管理用戶,加入下麵2行 進入druid後臺就需要登錄
        //servletRegistrationBean.addInitParameter("loginUsername", "admin");
        //servletRegistrationBean.addInitParameter("loginPassword", "admin");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }

    @Bean
    public StatFilter statFilter(){
        StatFilter statFilter = new StatFilter();
        statFilter.setLogSlowSql(true); //slowSqlMillis用來配置SQL慢的標準,執行時間超過slowSqlMillis的就是慢。
        statFilter.setMergeSql(true); //SQL合併配置
        statFilter.setSlowSqlMillis(1000);//slowSqlMillis的預設值為3000,也就是3秒。
        return statFilter;
    }

    @Bean
    public WallFilter wallFilter(){
        WallFilter wallFilter = new WallFilter();
        //允許執行多條SQL
        WallConfig config = new WallConfig();
        config.setMultiStatementAllow(true);
        wallFilter.setConfig(config);
        return wallFilter;
    }




}

 

 四、分別配置每個數據源對應的sqlSessionFactory,以及MapperScan掃描的包:

MybatisDatasourceConfig.java

package com.zjt.config;

import com.zjt.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

/**
 * @author <a href="zhaojiatao"></a>
 * @version 1.0, 2017/11/24
 * @description
 */
@Configuration
// 精確到 mapper 目錄,以便跟其他數據源隔離
@MapperScan(basePackages = "com.zjt.mapper", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisDatasourceConfig {

    @Autowired
    @Qualifier("systemDataSource")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory()); // 使用上面配置的Factory
        return template;
    }

    //關於事務管理器,不管是JPA還是JDBC等都實現自介面 PlatformTransactionManager
    // 如果你添加的是 spring-boot-starter-jdbc 依賴,框架會預設註入 DataSourceTransactionManager 實例。
    //在Spring容器中,我們手工註解@Bean 將被優先載入,框架不會重新實例化其他的 PlatformTransactionManager 實現類。
    /*@Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源
        // 與DataSourceTransactionManager引用的數據源一致即可,否則事務管理會不起作用。
        return new DataSourceTransactionManager(ds);

    }*/

}

 

 MybatisDatasource2Config.java

package com.zjt.config;

import com.zjt.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

/**
 * @author <a href="zhaojiatao"></a>
 * @version 1.0, 2017/11/24
 * @description
 */
@Configuration
// 精確到 mapper 目錄,以便跟其他數據源隔離
@MapperScan(basePackages = "com.zjt.mapper2", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDatasource2Config {

    @Autowired
    @Qualifier("businessDataSource")
    private DataSource ds;

    @Bean
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper2/*.xml"));
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
        return template;
    }

    //關於事務管理器,不管是JPA還是JDBC等都實現自介面 PlatformTransactionManager
    // 如果你添加的是 spring-boot-starter-jdbc 依賴,框架會預設註入 DataSourceTransactionManager 實例。
    //在Spring容器中,我們手工註解@Bean 將被優先載入,框架不會重新實例化其他的 PlatformTransactionManager 實現類。
    /*@Bean(name = "transactionManager2")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源
        // 與DataSourceTransactionManager引用的數據源一致即可,否則事務管理會不起作用。
        return new DataSourceTransactionManager(ds);
    }*/

}

 

 四、由於我們本例中只使用一個事務管理器:xatx,故就不在使用TxAdviceInterceptor.java和TxAdvice2Interceptor.java中配置的事務管理器了;有需求的童鞋可以自己配置其他的事務管理器;

五、新建分散式業務測試介面JtaTestService.java和實現類JtaTestServiceImpl.java

其實就是一個很簡單的test01()方法,在該方法中我們分別先後調用classService.saveOrUpdateTClass(tClass);和teacherService.saveOrUpdateTeacher(teacher);

實現先後操作兩個數據源:然後我們可以自己debug跟蹤事務的提交時機,此外,也可以在在兩個方法全執行結束之後,手動製造一個運行時異常,來檢查分散式事務是否全部回滾;

註意:

在實現類的方法中我使用的是:

@Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })

從而指定了使用哪個事務管理器,事務隔離級別(一般都用我這個預設的),回滾的條件(一般可以使用Exception),這三個可以自己根據業務實際修改;

package com.zjt.service3;

import java.util.Map;

public interface JtaTestService {

    public Map<String,Object> test01();

}
package com.zjt.service3.impl;


import com.zjt.entity.TClass;
import com.zjt.entity.Teacher;
import com.zjt.service.TClassService;
import com.zjt.service2.TeacherService;
import com.zjt.service3.JtaTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.LinkedHashMap;
import java.util.Map;

@Service("jtaTestServiceImpl")
public class JtaTestServiceImpl implements JtaTestService{

    @Autowired
    @Qualifier("teacherServiceImpl")
    private TeacherService teacherService;
    @Autowired
    @Qualifier("tclassServiceImpl")
    private TClassService tclassService;

    @Override
    @Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
    public Map<String, Object> test01() {
        LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
        TClass tClass=new TClass();
        tClass.setName("8888");
        tclassService.saveOrUpdateTClass(tClass);

        Teacher teacher=new Teacher();
        teacher.setName("8888");
        teacherService.saveOrUpdateTeacher(teacher);

        System.out.println(1/0);

        resultMap.put("state","success");
        resultMap.put("message","分散式事務同步成功");
        return resultMap;
    }
}

 

六、建立JtaTestContoller.java,接受一個來自前端的http請求,觸發JtaTestService 的test01方法:

package com.zjt.web;


import com.zjt.service3.JtaTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.LinkedHashMap;
import java.util.Map;

@Controller
@RequestMapping("/jtaTest")
public class JtaTestContoller {

    @Autowired
    @Qualifier("jtaTestServiceImpl")
    private JtaTestService taTestService;



    @ResponseBody
    @RequestMapping("/test01")
    public Map<String,Object> test01(){
        LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
        try {
            return taTestService.test01();
        }catch (Exception e){
            resultMap.put("state","fail");
            resultMap.put("message","分散式事務同步失敗");
            return resultMap;
        }
    }






}

 

 

 七、在test.ftl中增加一個按鈕來測試;

//分散式事務測試
        $("#JTATest").click(function(){
            $.ajax({
                type: "POST",
                url: "${basePath!}/jtaTest/test01",
                data: {}    ,
                async: false,
                error: function (request) {
                    layer.alert("與伺服器連接失敗/(ㄒoㄒ)/~~");
                    return false;
                },
                success: function (data) {
                    if (data.state == 'fail') {
                        layer.alert(data.message);
                        return false;
                    }else if(data.state == 'success'){
                        layer.alert(data.message);
                    }
                }
            });
        });




<button class="layui-btn" id="JTATest">同時向班級和老師表插入名為8888的班級和老師</button>

 

 

八、啟動服務,驗證結果:

 

 點擊這個按鈕,跳轉到controller:

當正常執行了sql語句之後,我們可以發現資料庫並沒有變化,因為整個方法的事務還沒有走完,當我們走到1/0這步時:

拋出運行時異常,並被spring事務攔截器攔截,並捕獲異常:

在this.completeTransactionAfterThrowing(txInfo, var16);方法中會將事務全部回滾:

22:09:04.243 logback [http-nio-8080-exec-5] INFO c.a.i.imp.CompositeTransactionImp - rollback() done of transaction 192.168.1.103.tm0000400006

 

此時,當我們再次打開資料庫驗證,依舊沒有變化,證明分散式事務配置成功;

大家可以基於我的代碼自己練習一下,自己嘗試著使用多事務管理器的情況下的靈活配置;

 九、後記:

 

 

本文源代碼:https://github.com/zhaojiatao/springboot-zjt-chapter10-springboot-atomikos-mysql-mybatis-druid.git

 

代碼在tomcat和jetty環境下均可完成事務回滾;

在事務回滾時可能報一個Transactional not active的警告,我google後,老外也說不出這個具體作用,大部分人認為這隻是一個警告,可以忽略;大家誰懂得或者仔細翻閱源代碼後懂得的,可以告訴我一下。謝謝

 


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

-Advertisement-
Play Games
更多相關文章
  • #之所以把這倆寫一起,並不是因為這倆有什麼關係,因為都太簡單,沒什麼可說的 #自定義函數的格式,def開頭,後面空格,在後面是函數名,接括弧,括弧里是入參參數 結果是1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n 說明下,這裡有個遞歸,遞歸在代碼里最好不要出現,因為每遞歸一次都會占用 ...
  • 2018-02-03 還好寫過大整數運算,順利地一編A過。(這道題就是一道大整數乘法的實現代碼) 簡單的說一下吧。大整數運算的思想就是用數組儲存數字,並且依靠數組進行進位的模擬。 當然這個用的是每1個存儲一格,要想優化時間的話可以考慮每4個存儲一格。 ...
  • Spring提供了一個工具類可以載入classpath下的文件,一般情況下無任何問題,但是當它作為公共的jar包中的工具來載入jar包中的文件時則報出找不到文件的錯誤. 點開看了一下這個工具類ResouceUtils.getFile()方法的源碼: 看了一下代碼結構簡單邏輯清晰,可能有問題的也就是上 ...
  • 一.創建表 django中ORM和資料庫的映射關係: 表名 <-------> 類名 欄位 <-------> 屬性 表記錄 <------->類實例對象 創建表就是一個創建類的過程: 外鍵,一對一,多對多: 二.添加記錄 三.查詢記錄 3.1 查詢相關API 3.2 基於對象的跨表查詢(子查詢) ...
  • 許久沒有寫了,雖然每天都有在學,但是學的東西也少了,後面難度慢慢加大,學習速度也是變慢了。這是許多天積累下來的筆記,從第一次接觸對象,到慢慢去瞭解,現在處於還待深入瞭解的狀態。萬物皆對象,那是不是說沒有對象的小伙伴不必擔心了呢? 萬物皆對象 終於到了對象這裡。面向對象程式設計(簡稱OOP),Java ...
  • 1 Mybatis的動態SQL簡介 2 if標簽 3 where標簽 4 trim標簽 5 choose-when-otherwise標簽(選擇分支) 6 set標簽 7 foreach標簽 8 內置參數 9 bind標簽 10 sql標簽 ...
  • 該系列教程系個人原創,並完整發佈在個人官網 "劉江的博客和教程" 所有轉載本文者,需在頂部顯著位置註明原作者及www.liujiangblog.com官網地址。 Python及Django學習QQ群:453131687 本章以創建一個Web投票應用為例子,手把手的教你如何使用Django開發Web應 ...
  • Python 學習的第一天 寫此博客 是為了激勵自己,並且將自己的心得以及遇到的問題與人分享 一、課堂筆記 1.Python 3.0 和 Python 2.0 不相容 Python 2.6 和 Python 2.7 是 Python 的過度版本 2.有關Python 的安裝以及helloworld ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...