我真的不想再用mybatis和其衍生框架了選擇自研亦是一種解脫

来源:https://www.cnblogs.com/xuejiaming/archive/2023/07/26/17576198.html
-Advertisement-
Play Games

# 我真的不想再用mybatis和其衍生框架了選擇自研亦是一種解脫 [文檔地址](https://xuejm.gitee.io/easy-query-doc/) https://xuejm.gitee.io/easy-query-doc/ [GITHUB地址](https://github.com/ ...


我真的不想再用mybatis和其衍生框架了選擇自研亦是一種解脫

文檔地址 https://xuejm.gitee.io/easy-query-doc/

GITHUB地址 https://github.com/xuejmnet/easy-query

GITEE地址 https://gitee.com/xuejm/easy-query

為什麼要用orm

眾所鄒知orm的出現讓本來以sql實現的複雜繁瑣功能大大簡化,對於大部分程式員而言一個框架的出現是為了生產力的提升.。dbc定義了交互資料庫的規範,任何資料庫的操作都是只需要滿足jdbc規範即可,而orm就是為了將jdbc的操作進行簡化。我個人“有幸”體驗過.net和java的兩個大orm,只能說差距很大,當然語言上的一些特性也讓java在實現orm上有著比較慢的進度,譬如泛型的出現,lambda的出現。

一個好的orm我覺得需要滿足以下幾點

  • 強類型,如果不支持強類型那麼和手寫sql沒有區別
  • 能實現80%的純手寫sql的功能,好的orm需要覆蓋業務常用功能
  • 支持泛型,“如果一個orm連泛型都不支持那麼就沒有必要存在”這是一句現實但是又很殘酷的結論,但是泛型會大大的減少開發人員的編寫錯誤率
  • 不應該依賴過多的組件,當然這並不是orm特有的,任何一個庫其實依賴越少越不易出bug

其實說了這麼多總結一下就是一個好的orm應該有ide的提示外加泛型約束幫助開發可以非常順滑的把代碼寫下去,並且錯誤部分可以完全的在編譯期間提現出來,運行時錯誤應該儘可能少的去避免。

為什麼放棄mybatis

首先如果你用過其他語言的orm那麼再用java的mybatis就像你用慣了java的stream然後去自行處理數據過濾,就像你習慣了kotlin的語法再回到java語法,很難受。這種難受不是自動擋到手動擋的差距,而且自動擋到手推車的差距。

xml配置sql也不知道是哪個“小天才”想出來的,先不說寫代碼的時候java代碼和xml代碼跳來跳去,而且xml下>,<必須要配合CDATA不然xml解析就失敗,別說轉義,我寫那玩意在加轉義你確定讓我後續看得眼睛不要累死嗎?美名其曰xml和代碼分離方便維護,但是你再怎麼方便修改了代碼一樣需要重啟,並且因為代碼寫在xml裡面導致動態條件得能力相對很弱。並且我也不知道mybatis為什麼天生不支持分頁,需要分頁插件來支持,難道一個3202年的orm了還需要這樣嗎,很難搞懂mybatis的作者難道不寫crud代碼的嗎?有些時候簡潔並不是偷懶的原因,當然也有可能是架構的問題導致的。

邏輯刪除的功能我覺得稍微正常一點的企業一定都會有這個功能,但是因為使用了myabtis,因為手寫sql,所以常常會忘記往sql中添加邏輯刪除欄位,從而導致一些奇奇怪怪的bug需要排查,因為這些都是編譯器無法體現的錯誤,因為他是字元串,因為mybatis把這個問題的原因指向了用戶,這一點他很聰明,這個是用戶的錯誤而不是框架的,但是框架要做的就是儘可能的將一些重覆工作進行封裝隱藏起來自動完成。

可能又會有一些用戶會說所見即所得這樣我才能知道他怎麼執行了,但是現在哪個orm沒有sql列印功能,哪個orm框架執行的sql和列印的sql是不一樣的,不是所見即所得。總體而言我覺得mybatis充其量算是sqltemlate,比sqlhelper好的地方就是他是參數化防止sql註入。當然最主要的呀一點事難道java程式員不需要修改表,不需要動表結構,不需要後期維護的嗎還是說java程式員寫一個項目就換一個地方跳槽,還是說java程式員每個方法都有單元測試。我在轉java後理解了一點,原來這就是你們經常說的java加班嚴重,用這種框架加班不嚴重就有鬼了。

為什麼放棄mybatis衍生框架

有幸在201幾年再網上看到了mybatis-plus框架,這塊框架一齣現就吸引了我,因為他在處理sql的方式上和.net的orm很相似,起碼都是強類型,起碼不需要java文件和xml文件跳來跳去,平常50%的代碼也是可以通過框架的lambda表達式來實現,我個人比較排斥他的字元串模式的querywrapper,因為一門強類型語言缺少了強類型提示,在編寫代碼的時候會非常的奇怪。包括後期的重構,當然如果你的代碼後續不需要你維護那麼我覺得你用哪種方式都是ok的反正是一次性的,能出來結果就好了。

繼續說mybatis-plus,因為工作的需要再2020年左右針對內部框架進行改造,並且讓mybatis-plus支持強類型group by,sum,min,max,any等api。

這個時候其實大部分情況下已經可以應對了,就這樣用了1年左右這個框架,包括後續的update的increment,decrement

update table set column=column-1 where id=xxx and column>1

全部使用lambda強類型語法,可以應對多數情況,但是針對join始終沒有一個很好地方法。直到我遇到了mpj也就是mybatis-plus-join,但是這個框架也有問題,就是這個邏輯刪除在join的子表上不生效,需要手動處理,如果生效那麼在where上面,不知道現在怎麼樣了,當時我也是自行實現了讓其出現在join的on後面,但是因為實現是需要實現某個介面的,所以並沒有pr代碼.
首先定義一個介面

public interface ISoftDelete {
    Boolean getDeleted();
}

//其中join mapper是我自己的實現,主要還是`WrapperFunction`的那段定義
  @Override
    public Scf4jBaseJoinLinq<T1,TR> on(WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> onFunction) {
        WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> join=  on->{
            MPJAbstractLambdaWrapper<T1, ?> apply = onFunction.apply(on);
            if(ISoftDelete.class.isAssignableFrom(joinClass)){
                SFunction deleted = LambdaHelper.getFunctionField(joinClass, "deleted", Boolean.class);
                apply.eq(deleted,false);
            }
            return apply;
        };
        joinMapper.setJoinOnFunction(query->{
            query.innerJoin(joinClass,join);
        });
        return joinMapper;
    }

雖然實現了join但是還是有很多問題出現和bug。

  • 比如不支持vo對象的返回,只能返回資料庫對象自定義返回列,不然就是查詢所有列
  • 再比如如果你希望你的對象update的時候填充null到資料庫,那麼只能在entity欄位上添加,這樣就導致這個欄位要麼全部生效要麼全部不生效.
  • 批量插入不支持預設居然是foreach一個一個加,當然這也沒關係,但是你真的想實現批處理需要自己編寫很複雜的代碼並且需要支持全欄位。而不是null列不填充
  • MetaObjectHandler,支持entityinsertupdate但是不支持lambdaUpdateWrapper,有時候當前更新人和更新時間都是需要的,你也可以說資料庫可以設置最後更新時間,但是最後修改人呢?
  • 非常複雜的動態表名,拜托大哥我只是想改一下表名,目前的解決方案就是try-finally每次用完都需要清理一下當前線程,因為tomcat會復用線程,通過threadlocal來實現,話說pagehelper應該也是這種方式實現的吧
    當然其他還有很多問題導致最終我沒辦法忍受,選擇了自研框架,當然我的框架自研是參考了一部分的freesql和sqlsuagr的api,並且還有java的beetsql的實現和部分方法。畢竟站在巨人的肩膀上才能看的更遠,不要問我為什麼不參考mybatis的,我覺得mybatis已經把簡單問題複雜化了,如果需要看懂他的代碼是一件很得不償失的事情,最終我發現我的選擇是正確的,我通過參考beetsql的源碼很快的清楚了java這邊應該需要做的事情,為我編寫後續框架節約了太多時間,這邊也給beetsql打個廣告 https://gitee.com/xiandafu/beetlsql

自研orm有哪些特點

easy-query一款無任何依賴的java全新高性能orm支持 單表 多表 子查詢 邏輯刪除 多租戶 差異更新 聯級一對一 一對多 多對一 多對多 分庫分表(支持跨表查詢分頁等) 動態表名 資料庫列高效加解密支持like crud攔截器 原子更新 vo對象直接返回

文檔地址 https://xuejm.gitee.io/easy-query-doc/

GITHUB地址 https://github.com/xuejmnet/easy-query

GITEE地址 https://gitee.com/xuejm/easy-query

  • 強類型,可以幫助團隊在構建和查詢數據的時候擁有id提示,並且易於後期維護。
  • 泛型可以控制我們編寫代碼時候的一些低級錯誤,比如我只查詢一張表,但是where語句裡面可以使用不存在上下文的表作為條件,進一步限制和加強表達式
  • easy-query提供了三種模式分別是lambda,property,apt proxy其中lambda表達式方便重構維護,property只是性能最好,apt proxy方便維護,但是重構需要一起重構apt文件

單表查詢

//根據條件查詢表中的第一條記錄
List<Topic> topics = easyQuery
                .queryable(Topic.class)
                .limit(1)
                .toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LIMIT 1
<== Total: 1

//根據條件查詢id為3的集合
List<Topic> topics = easyQuery
                .queryable(Topic.class)
                .where(o->o.eq(Topic::getId,"3").eq(Topic::geName,"4")
                .toList();

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t WHERE t.`id` = ? AND t.`name` = ?
==> Parameters: 3(String),4(String)
<== Total: 1

多表

 Topic topic = easyQuery
                .queryable(Topic.class)
                //join 後面是雙參數委托,參數順序表示join表順序,可以通過then函數切換
                .leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
                .where(o -> o.eq(Topic::getId, "3"))
                .firstOrNull();

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LEFT JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t.`id` = ? LIMIT 1
==> Parameters: 3(String)
<== Total: 1

List<BlogEntity> blogEntities = easyQuery
                .queryable(Topic.class)
                //join 後面是雙參數委托,參數順序表示join表順序,可以通過then函數切換
                .innerJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
                .where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
                //join查詢select必須要帶對應的返回結果,可以是自定義dto也可以是實體對象,如果不帶對象則返回t表主表數據
                .select(BlogEntity.class, (t, t1) -> t1.columnAll())
                .toList();

==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic t INNER JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: 3(String)
<== Total: 1

子查詢


```java
//SELECT * FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
 Queryable<BlogEntity> subQueryable = easyQuery.queryable(BlogEntity.class)
                .where(o -> o.eq(BlogEntity::getId, "1"));


List<Topic> x = easyQuery
        .queryable(Topic.class).where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId)))).toList();


==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`)
==> Parameters: false(Boolean),1(String)
<== Time Elapsed: 3(ms)
<== Total: 1


//SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
Queryable<String> idQueryable = easyQuery.queryable(BlogEntity.class)
            .where(o -> o.eq(BlogEntity::getId, "123"))
            .select(String.class, o -> o.column(BlogEntity::getId));//如果子查詢in string那麼就需要select string,如果integer那麼select要integer 兩邊需要一致
List<Topic> list = easyQuery
        .queryable(Topic.class).where(o -> o.in(Topic::getId, idQueryable)).toList();


==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`id` IN (SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?)
==> Parameters: false(Boolean),123(String)
<== Time Elapsed: 2(ms)
<== Total: 0

自定義邏輯刪除



//@Component //如果是spring
public class MyLogicDelStrategy extends AbstractLogicDeleteStrategy {
    /**
     * 允許datetime類型的屬性
     */
    private final Set<Class<?>> allowTypes=new HashSet<>(Arrays.asList(LocalDateTime.class));
    @Override
    protected SQLExpression1<WherePredicate<Object>> getPredicateFilterExpression(LogicDeleteBuilder builder,String propertyName) {
        return o->o.isNull(propertyName);
    }

    @Override
    protected SQLExpression1<ColumnSetter<Object>> getDeletedSQLExpression(LogicDeleteBuilder builder, String propertyName) {
//        LocalDateTime now = LocalDateTime.now();
//        return o->o.set(propertyName,now);
        //上面的是錯誤用法,將now值獲取後那麼這個now就是個固定值而不是動態值
        return o->o.set(propertyName,LocalDateTime.now())
                .set("deletedUser",CurrentUserHelper.getUserId());
    }

    @Override
    public String getStrategy() {
        return "MyLogicDelStrategy";
    }

    @Override
    public Set<Class<?>> allowedPropertyTypes() {
        return allowTypes;
    }
}

//為了測試防止數據被刪掉,這邊採用不存在的id
logicDelTopic.setId("11xx");
//測試當前人員
CurrentUserHelper.setUserId("easy-query");
long l = easyQuery.deletable(logicDelTopic).executeRows();

==> Preparing: UPDATE t_logic_del_topic_custom SET `deleted_at` = ?,`deleted_user` = ? WHERE `deleted_at` IS NULL AND `id` = ?
==> Parameters: 2023-04-01T23:15:13.944(LocalDateTime),easy-query(String),11xx(String)
<== Total: 0

差異更新

  • 要註意是否開啟了追蹤spring-boot下用@EasyQueryTrack註解即可開啟
  • 是否將當前對象添加到了追蹤上下文 查詢添加asTracking或者 手動將查詢出來的對象進行easyQuery.addTracking(Object entity)
TrackManager trackManager = easyQuery.getRuntimeContext().getTrackManager();
try{
        trackManager.begin();
        Topic topic = easyQuery.queryable(Topic.class)
                .where(o -> o.eq(Topic::getId, "7")).asTracking().firstNotNull("未找到對應的數據");
        String newTitle = "test123" + new Random().nextInt(100);
        topic.setTitle(newTitle);
        long l = easyQuery.updatable(topic).executeRows();
}finally {

        trackManager.release();
}
==> Preparing: UPDATE t_topic SET `title` = ? WHERE `id` = ?
==> Parameters: test1239(String),7(String)
<== Total: 1

關聯查詢

一對一

學生和學生地址

//資料庫對像查詢
           List<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
                        .toList();
//vo自定義列映射返回
List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
                        .select(SchoolStudentVO.class,o->o.columnAll()
                                .columnInclude(SchoolStudent::getSchoolStudentAddress,SchoolStudentVO::getSchoolStudentAddress))
                        .toList();

多對一

學生和班級

//資料庫對像查詢
 List<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolClass))
                        .toList();
//自定義列
 List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolClass))
                        .select(SchoolStudentVO.class,o->o
                                .columnAll()
                                .columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass,s->s.column(SchoolClassVO::getId))
                        )
                        .toList();

//vo自定義列映射返回
   List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolClass))
                        .select(SchoolStudentVO.class,o->o
                                .columnAll()
                                .columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass)
                        )
                        .toList();

一對多

班級和學生

//資料庫對像查詢
 List<SchoolClass> list1 = easyQuery.queryable(SchoolClass.class)
                        .include(o -> o.many(SchoolClass::getSchoolStudents))
                        .toList();
//vo自定義列映射返回
       List<SchoolClassVO> list1 = easyQuery.queryable(SchoolClass.class)
                        .include(o -> o.many(SchoolClass::getSchoolStudents))
                        .select(SchoolClassVO.class,o->o.columnAll()
                                .columnIncludeMany(SchoolClass::getSchoolStudents,SchoolClassVO::getSchoolStudents))
                        .toList();

多對多

班級和老師

      List<SchoolClass> list2 = easyQuery.queryable(SchoolClass.class)
                .include(o -> o.many(SchoolClass::getSchoolTeachers,1))
                .toList();
  List<SchoolClassVO> list2 = easyQuery.queryable(SchoolClass.class)
                    .include(o -> o.many(SchoolClass::getSchoolTeachers))
                    .select(SchoolClassVO.class,o->o.columnAll()
                            .columnIncludeMany(SchoolClass::getSchoolTeachers,SchoolClassVO::getSchoolTeachers))
                    .toList();

動態報名

List<BlogEntity> blogEntities = easyQuery.queryable(BlogEntity.class)
                .asTable(a -> "aa_bb_cc")
                .where(o -> o.eq(BlogEntity::getId, "123")).toList();


==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
<== Total: 0



List<BlogEntity> blogEntities = easyQuery.queryable(BlogEntity.class)
                .asTable(a->{
                    if("t_blog".equals(a)){
                        return "aa_bb_cc1";
                    }
                    return "xxx";
                })
                .where(o -> o.eq(BlogEntity::getId, "123")).toList();


==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc1 t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
<== Total: 0




List<BlogEntity> x_t_blog = easyQuery
                .queryable(Topic.class)
                .asTable(o -> "t_topic_123")
                .innerJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
                .asTable("x_t_blog")
                .where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
                .select(BlogEntity.class, (t, t1) -> t1.columnAll()).toList();

==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic_123 t INNER JOIN x_t_blog t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: false(Boolean),3(String)
<== Total: 0

最後

感謝各位看到最後,希望以後我的開源框架可以幫助到您,如果您覺得有用可以點點star,這將對我是極大的鼓勵

更多文檔信息可以參考git地址或者文檔

文檔地址 https://xuejm.gitee.io/easy-query-doc/

GITHUB地址 https://github.com/xuejmnet/easy-query

GITEE地址 https://gitee.com/xuejm/easy-query


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

-Advertisement-
Play Games
更多相關文章
  • 最近在工作的過程中,遇到了不少奇怪自己或者同事的Bug,都是一些出乎意料的,不太容易發現的,記錄一下來幫助可能也遇到了這些Bug的人 # 1. 編譯時泛型校驗失效 ```java Map nameToType = new HashMap(); nameToType.put( "testName", ...
  • Notion這款筆記軟體相信很多開發者都比較熟悉了,很多讀者,包括我自己都用它來記錄和管理自己的筆記。今天給大家推薦一個最近比較火的開源替代方案:AFFiNE。目前該開源項目已經斬獲20.1K Star,熱度非常的高,下麵一起來認識一下這個繼Notion之後,被熱捧的開源軟體吧。 ![](https ...
  • ### 1.eval() 功能描述:“剝去字元串的外衣”,去運行字元串裡面的代碼 作用 : (1)參數是一個類似"1+3"這樣數學表達式的字元串,可以計算得到返回值(int型) (2)參數是一個類似"{'name':'tian','age':18}"這樣字典、列表、元組外套上一對引號的字元串,可以快 ...
  • 昨晚一回家,表弟就神神秘秘的跟我說,發現一個高顏值網站,非要拉著我研究一下她們的顏值高低。 我心想,這還得要我一個個慢慢看,太麻煩了~ 於是反手用Python給他寫了一個人臉識別代碼,把她們的照片全部爬下來,自動檢測顏值打分排名。 這不比手動快多了? 準備工作 開發環境 Python 3.8 Pyc ...
  • ## 1 什麼是緩存 第一個問題,首先要搞明白什麼是緩存,緩存的意義是什麼。 對於普通業務,如果要查詢一個數據,一般直接select資料庫進行查找。但是在高流量的情況下,直接查找資料庫就會成為性能的瓶頸。因為資料庫查找的流程是先要從磁碟拿到數據,再刷新到記憶體,再返回數據。磁碟相比於記憶體來說,速度是很 ...
  • 來源:juejin.cn/post/7026372482110079012 ## 前言 > 互聯網的連接速度慢且不穩定,有可能由於網路故障導致斷開連接。 > > 在客戶端下載一個大對象時,因網路斷開導致上傳下載失敗的概率就會變得不可忽視。 ![](https://img2023.cnblogs.co ...
  • ## 教程簡介 Google圖表是一個純粹的基於JavaScript的圖表庫,旨在通過添加互動式製圖功能來增強Web應用程式。它支持多種圖表。在Chrome,Firefox,Safari,Internet Explorer(IE)等標準瀏覽器中使用SVG繪製圖表。在傳統IE 6中,VML用於繪製圖形 ...
  • ## 問題描述 分享一個發版過程服務報錯問題,問題出現在每次發版,服務準備下線的時候,報錯的位置是在將任務submit提交給線程池,使用Future.get()引發的TimeoutException,錯誤日誌會列印下麵的"error"。偽代碼如下: ``` List>>> futures = new ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...