flutter3-dylive仿抖音App實例|Flutter3+Getx實戰短視頻直播應用

来源:https://www.cnblogs.com/xiaoyan2017/p/18092224
-Advertisement-
Play Games

原創研發flutter3+getX+mediaKit仿抖音app短視頻直播實戰Flutter-DouYin。 flutter3_dylive使用最新跨平臺技術flutter3.x+dart3+getx+get_storage+media_kit開發手機端仿抖音app小視頻直播實戰項目。實現了抖音全屏 ...


原創研發flutter3+getX+mediaKit仿抖音app短視頻直播實戰Flutter-DouYin

flutter3_dylive使用最新跨平臺技術flutter3.x+dart3+getx+get_storage+media_kit開發手機端仿抖音app小視頻直播實戰項目。實現了抖音全屏式上下滑動視頻、左右滑動切換頁面模塊,直播間進場/禮物動畫,聊天等模塊。

技術棧

  • 編輯器:Vscode
  • 技術框架:Flutter3.19.2+Dart3.3.0
  • 路由/狀態管理:get: ^4.6.6
  • 本地緩存:get_storage: ^2.1.1
  • 圖片預覽插件:photo_view: ^0.14.0
  • 刷新載入:easy_refresh^3.3.4
  • toast輕提示:toast^0.3.0
  • 視頻套件:media_kit: ^1.1.10+1

flutter_douyin實現了類似抖音全屏沉浸式滑動效果(上下滑動視頻、左右切換頁面模塊)。

如下圖:實現了左右滑動的同時,頂部狀態欄+Tab菜單+底部bottomNavigationBar導航欄三者聯動效果。

項目結構目錄

在項目創建前期,需要配置好Flutter SDK和Dart SDK開發環境。

https://flutter.dev/

https://www.dartcn.com/

https://pub.flutter-io.cn/

如果是使用Vscode編輯器開發flutter項目,大家需自行配置flutter/dart擴展語法插件。

項目中的一些技術知識點,有些是在之前的項目flutter3聊天實例中有介紹過,感興趣的話可以去看看。

https://www.cnblogs.com/xiaoyan2017/p/18008370

https://www.cnblogs.com/xiaoyan2017/p/18048244

Flutter入口配置main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:media_kit/media_kit.dart';

import 'utils/index.dart';

// 引入佈局模板
import 'layouts/index.dart';

import 'binding/binding.dart';

// 引入路由管理
import 'router/index.dart';

void main() async {
  // 初始化get_storage
  await GetStorage.init();

  // 初始化media_kit
  WidgetsFlutterBinding.ensureInitialized();
  MediaKit.ensureInitialized();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'FLUTTER3 DYLIVE',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFFE2C55)),
        useMaterial3: true,
        // 修正windows端字體粗細不一致
        fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null,
      ),
      home: const Layout(),
      // 全局綁定GetXController
      initialBinding: GlobalBindingController(),
      // 初始路由
      initialRoute: Utils.isLogin() ? '/' : '/login',
      // 路由頁面
      getPages: routePages,
      // 錯誤路由
      // unknownRoute: GetPage(name: '/404', page: Error),
    );
  }
}

Flutter3底部導航欄

底部導航欄使用 bottomNavigationBar 組件實現頁面模塊切換。通過getx全局狀態來聯動控制底部導航欄背景顏色。

導航欄中間圖標/圖片按鈕,使用了 Positioned 組件定位實現功能。

return Scaffold(
  backgroundColor: Colors.grey[50],
  body: pageList[pageCurrent],
  // 底部導航欄
  bottomNavigationBar: Theme(
    // Flutter去掉BottomNavigationBar底部導航欄的水波紋
    data: ThemeData(
      splashColor: Colors.transparent,
      highlightColor: Colors.transparent,
      hoverColor: Colors.transparent,
    ),
    child: Obx(() {
      return Stack(
        children: [
          Container(
            decoration: const BoxDecoration(
              border: Border(top: BorderSide(color: Colors.black54, width: .1)),
            ),
            child: BottomNavigationBar(
              backgroundColor: bottomNavigationBgcolor(),
              fixedColor: FStyle.primaryColor,
              unselectedItemColor: bottomNavigationItemcolor(),
              type: BottomNavigationBarType.fixed,
              elevation: 1.0,
              unselectedFontSize: 12.0,
              selectedFontSize: 12.0,
              currentIndex: pageCurrent,
              items: [
                ...pageItems
              ],
              onTap: (index) {
                setState(() {
                  pageCurrent = index;
                });
              },
            ),
          ),
          // 自定義底部導航欄中間按鈕
          Positioned(
            left: MediaQuery.of(context).size.width / 2 - 15,
            top: 0,
            bottom: 0,
            child: InkWell(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // Icon(Icons.tiktok, color: bottomNavigationItemcolor(centerDocked: true), size: 32.0,),
                  Image.asset('assets/images/applogo.png', width: 32.0, fit: BoxFit.contain,)
                  // Text('直播', style: TextStyle(color: bottomNavigationItemcolor(centerDocked: true), fontSize: 12.0),)
                ],
              ),
              onTap: () {
                setState(() {
                  pageCurrent = 2;
                });
              },
            ),
          ),
        ],
      );
    }),
  ),
);
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../styles/index.dart';
import '../../controllers/page_video_controller.dart';

// 引入pages頁面
import '../pages/index/index.dart';
import '../pages/video/index.dart';
import '../pages/live/index.dart';
import '../pages/message/index.dart';
import '../pages/my/index.dart';

class Layout extends StatefulWidget {
  const Layout({super.key});

  @override
  State<Layout> createState() => _LayoutState();
}

class _LayoutState extends State<Layout> {
  PageVideoController pageVideoController = Get.put(PageVideoController());

  // page索引
  int pageCurrent = 0;
  // page頁面
  List pageList = [const Index(), const FVideo(), const FLiveList(), const Message(), const My()];
  // tabs選項
  List pageItems = [
    const BottomNavigationBarItem(
      icon: Icon(Icons.home_outlined),
      label: '首頁'
    ),
    const BottomNavigationBarItem(
      icon: Icon(Icons.play_arrow_outlined),
      label: '短視頻'
    ),
    const BottomNavigationBarItem(
      icon: Icon(Icons.live_tv_rounded, color: Colors.transparent,),
      label: ''
    ),
    BottomNavigationBarItem(
      icon: Stack(
        alignment: const Alignment(4, -2),
        children: [
          const Icon(Icons.messenger_outline),
          FStyle.badge(1)
        ],
      ),
      label: '消息'
    ),
    BottomNavigationBarItem(
      icon: Stack(
        alignment: const Alignment(1.5, -1),
        children: [
          const Icon(Icons.person_outline),
          FStyle.badge(0, isdot: true)
        ],
      ),
      label: '我'
    )
  ];

  // 底部導航欄背景色
  Color bottomNavigationBgcolor() {
    int index = pageCurrent;
    int pageVideoTabIndex = pageVideoController.pageVideoTabIndex.value;
    Color color = Colors.white;
    if(index == 1) {
      if([1, 2, 3].contains(pageVideoTabIndex)) {
        color = Colors.white;
      }else {
        color = Colors.black;
      }
    }
    return color;
  }
  // 底部導航欄顏色
  Color bottomNavigationItemcolor({centerDocked = false}) {
    int index = pageCurrent;
    int pageVideoTabIndex = pageVideoController.pageVideoTabIndex.value;
    Color color = Colors.black54;
    if(index == 1) {
      if([1, 2, 3].contains(pageVideoTabIndex)) {
        color = Colors.black54;
      }else {
        color = Colors.white60;
      }
    }else if(index == 2 && centerDocked) {
      color = FStyle.primaryColor;
    }
    return color;
  }

  // ...
}

Flutter實現抖音全屏式滑動

如下圖:實現類似抖音左右滑動全屏切換頁面模塊、上下滑動絲滑切換短視頻。

採用 TabBar 和 PageView 聯動頁面滑動。

return Scaffold(
  extendBodyBehindAppBar: true,
  appBar: AppBar(
    forceMaterialTransparency: true,
    backgroundColor: [1, 2, 3].contains(pageVideoController.pageVideoTabIndex.value) ? null : Colors.transparent,
    foregroundColor: [1, 2, 3].contains(pageVideoController.pageVideoTabIndex.value) ? Colors.black : Colors.white,
    titleSpacing: 1.0,
    leading: Obx(() => IconButton(icon: Icon(Icons.menu, color: tabColor(),), onPressed: () {},),),
    title: Obx(() {
      return TabBar(
        controller: tabController,
        tabs: pageTabs.map((v) => Tab(text: v)).toList(),
        isScrollable: true,
        tabAlignment: TabAlignment.center,
        overlayColor: MaterialStateProperty.all(Colors.transparent),
        unselectedLabelColor: unselectedTabColor(),
        labelColor: tabColor(),
        indicatorColor: tabColor(),
        indicatorSize: TabBarIndicatorSize.label,
        unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
        labelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w600),
        dividerHeight: 0,
        labelPadding: const EdgeInsets.symmetric(horizontal: 10.0),
        indicatorPadding: const EdgeInsets.symmetric(horizontal: 5.0),
        onTap: (index) {
          pageVideoController.updatePageVideoTabIndex(index); // 更新索引
          pageController.jumpToPage(index);
        },
      );
    }),
    actions: [
      Obx(() => IconButton(icon: Icon(Icons.search, color: tabColor(),), onPressed: () {},),),
    ],
  ),
  body: Column(
    children: [
      Expanded(
        child: Stack(
          children: [
            /// 水平滾動模塊
            PageView(
              // 自定義滾動行為(支持桌面端滑動、去掉滾動條槽)
              scrollBehavior: PageScrollBehavior().copyWith(scrollbars: false),
              scrollDirection: Axis.horizontal,
              controller: pageController,
              onPageChanged: (index) {
                pageVideoController.updatePageVideoTabIndex(index); // 更新索引
                setState(() {
                  tabController.animateTo(index);
                });
              },
              children: [
                ...pageModules
              ],
            ),
          ],
        ),
      ),
    ],
  ),
);
PageVideoController pageVideoController = Get.put(PageVideoController());

List<String> pageTabs = ['熱點', '長視頻', '文旅', '商城', '關註', '同城服務', '推薦'];
final pageModules = [
  const HotModule(),
  const LongVideoModule(),
  const TripModule(),
  const MallModule(),
  const FavorModule(),
  const NearModule(),
  const RecommendModule()
];
late final TabController tabController = TabController(initialIndex: pageVideoController.pageVideoTabIndex.value, length: pageTabs.length, vsync: this);
// 頁面controller
late final PageController pageController = PageController(initialPage: pageVideoController.pageVideoTabIndex.value, viewportFraction: 1.0);

@override
void dispose() {
  tabController.dispose();
  pageController.dispose();
  super.dispose();
}

flutter短視頻mini進度條

底部mini播放進度條一開始是採用自定義組件實現效果,不過不能拖動滾動條進度,後來就採用了Slider組件來實現。

// flutter滑動短視頻模塊  Q:282310962

return Container(
  color: Colors.black,
  child: Column(
    children: [
      Expanded(
        child: Stack(
          children: [
            /// 垂直滾動模塊
            PageView.builder(
              // 自定義滾動行為(支持桌面端滑動、去掉滾動條槽)
              scrollBehavior: PageScrollBehavior().copyWith(scrollbars: false),
              scrollDirection: Axis.vertical,
              controller: pageController,
              onPageChanged: (index) async {
                ...
              },
              itemCount: videoList.length,
              itemBuilder: (context, index) {
                return Stack(
                  children: [
                    // 視頻區域
                    Positioned(
                      top: 0,
                      left: 0,
                      right: 0,
                      bottom: 0,
                      child: GestureDetector(
                        child: Stack(
                          children: [
                            // 短視頻插件
                            Visibility(
                              visible: videoIndex == index,
                              child: Video(
                                controller: videoController,
                                fit: BoxFit.cover,
                                // 無控制條
                                controls: NoVideoControls,
                              ),
                            ),
                            // 播放/暫停按鈕
                            StreamBuilder(
                              stream: player.stream.playing,
                              builder: (context, playing) {
                                return Visibility(
                                  visible: playing.data == false,
                                  child: Center(
                                    child: IconButton(
                                      padding: EdgeInsets.zero,
                                      onPressed: () {
                                        player.playOrPause();
                                      },
                                      icon: Icon(
                                        playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
                                        color: Colors.white70,
                                        size: 70,
                                      ),
                                    ),
                                  ),
                                );
                              },
                            ),
                          ],
                        ),
                        onTap: () {
                          player.playOrPause();
                        },
                      ),
                    ),
                    // 右側操作欄
                    Positioned(
                      bottom: 15.0,
                      right: 10.0,
                      child: Column(
                        ...
                      ),
                    ),
                    // 底部信息區域
                    Positioned(
                      bottom: 15.0,
                      left: 10.0,
                      right: 80.0,
                      child: Column(
                        ...
                      ),
                    ),
                    // 播放mini進度條
                    Positioned(
                      bottom: 0.0,
                      left: 10.0,
                      right: 10.0,
                      child: Visibility(
                        visible: videoIndex == index && position > Duration.zero,
                        child: Listener(
                          child: SliderTheme(
                            data: const SliderThemeData(
                              trackHeight: 2.0,
                              thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0), // 調整滑塊的大小
                              // trackShape: RectangularSliderTrackShape(), // 使用矩形軌道形狀
                              overlayShape: RoundSliderOverlayShape(overlayRadius: 0), // 去掉Slider預設上下邊距間隙
                              inactiveTrackColor: Colors.white24, // 設置非活動進度條的顏色
                              activeTrackColor: Colors.white, // 設置活動進度條的顏色
                              thumbColor: Colors.pinkAccent, // 設置滑塊的顏色
                              overlayColor: Colors.transparent, // 設置滑塊覆蓋層的顏色
                            ),
                            child: Slider(
                              value: sliderValue,
                              onChanged: (value) async {
                                // debugPrint('當前視頻播放時間$value');
                                setState(() {
                                  sliderValue = value;
                                });
                                // 跳轉播放時間
                                await player.seek(duration * value.clamp(0.0, 1.0));
                              },
                              onChangeEnd: (value) async {
                                setState(() {
                                  sliderDraging = false;
                                });
                                // 繼續播放
                                if(!player.state.playing) {
                                  await player.play();
                                }
                              },
                            ),
                          ),
                          onPointerMove: (e) {
                            setState(() {
                              sliderDraging = true;
                            });
                          },
                        ),
                      ),
                    ),
                    // 視頻播放時間
                    Positioned(
                      bottom: 90.0,
                      left: 10.0,
                      right: 10.0,
                      child: Visibility(
                        visible: sliderDraging,
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Text(position.label(reference: duration), style: const TextStyle(color: Colors.white, fontSize: 16.0, fontWeight: FontWeight.w600),),
                            Container(
                              margin: const EdgeInsets.symmetric(horizontal: 7.0),
                              child: const Text('/', style: TextStyle(color: Colors.white54, fontSize: 10.0,),),
                            ),
                            Text(duration.label(reference: duration), style: const TextStyle(color: Colors.white54, fontSize: 16.0, fontWeight: FontWeight.w600),),
                          ],
                        ),
                      ),
                    ),
                  ],
                );
              },
            ),
            /// 固定層
            // 紅包
            Positioned(
              left: 15.0,
              top: MediaQuery.of(context).padding.top + 20,
              child: Container(
                height: 40.0,
                width: 40.0,
                decoration: BoxDecoration(
                  color: Colors.black12,
                  borderRadius: BorderRadius.circular(100.0),
                ),
                child: UnconstrainedBox(
                  child: Image.asset('assets/images/time-hb.png', width: 30.0, fit: BoxFit.contain,),
                ),
              ),
            ),
          ],
        ),
      ),
    ],
  ),
);

flutter3直播模塊

flutter直播模塊包含了頂部信息、底部進場提示+商品層+彈幕層+操作欄等幾個模塊。

// 商品購買動效
Container(
  ...
),

// 加入直播間動效
const AnimationLiveJoin(
  joinQueryList: [
    {'avatar': 'assets/images/logo.png', 'name': 'andy'},
    {'avatar': 'assets/images/logo.png', 'name': 'jack'},
    {'avatar': 'assets/images/logo.png', 'name': '一條鹹魚'},
    {'avatar': 'assets/images/logo.png', 'name': '四季平安'},
    {'avatar': 'assets/images/logo.png', 'name': '葉子'},
  ],
),

// 送禮物動效
const AnimationLiveGift(
  giftQueryList: [
    {'label': '小心心', 'gift': 'assets/images/gift/gift1.png', 'user': 'Jack', 'avatar': 'assets/images/avatar/uimg2.jpg', 'num': 12},
    {'label': '棒棒糖', 'gift': 'assets/images/gift/gift2.png', 'user': 'Andy', 'avatar': 'assets/images/avatar/uimg6.jpg', 'num': 36},
    {'label': '大啤酒', 'gift': 'assets/images/gift/gift3.png', 'user': '一條鹹魚', 'avatar': 'assets/images/avatar/uimg1.jpg', 'num': 162},
    {'label': '人氣票', 'gift': 'assets/images/gift/gift4.png', 'user': 'Flower', 'avatar': 'assets/images/avatar/uimg5.jpg', 'num': 57},
    {'label': '鮮花', 'gift': 'assets/images/gift/gift5.png', 'user': '四季平安', 'avatar': 'assets/images/avatar/uimg3.jpg', 'num': 6},
    {'label': '捏捏小臉', 'gift': 'assets/images/gift/gift6.png', 'user': 'Alice', 'avatar': 'assets/images/avatar/uimg4.jpg', 'num': 28},
    {'label': '你真好看', 'gift': 'assets/images/gift/gift7.png', 'user': '葉子', 'avatar': 'assets/images/avatar/uimg7.jpg', 'num': 95},
    {'label': '親吻', 'gift': 'assets/images/gift/gift8.png', 'user': 'YOYO', 'avatar': 'assets/images/avatar/uimg8.jpg', 'num': 11},
    {'label': '玫瑰', 'gift': 'assets/images/gift/gift12.png', 'user': '宇輝', 'avatar': 'assets/images/avatar/uimg9.jpg', 'num': 3},
    {'label': '私人飛機', 'gift': 'assets/images/gift/gift16.png', 'user': 'Hison', 'avatar': 'assets/images/avatar/uimg10.jpg', 'num': 273},
  ],
),

// 直播彈幕+商品講解
Container(
  margin: const EdgeInsets.only(top: 7.0),
  height: 200.0,
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.end,
    children: [
      Expanded(
        child: ListView.builder(
          padding: EdgeInsets.zero,
          itemCount: liveJson[index]['message']?.length,
          itemBuilder: (context, i) => danmuList(liveJson[index]['message'])[i],
        ),
      ),
      SizedBox(
        width: isVisibleGoodsTalk ? 7 : 35,
      ),
      // 商品講解
      Visibility(
        visible: isVisibleGoodsTalk,
        child: Column(
          ...
        ),
      ),
    ],
  ),
),

// 底部工具欄
Container(
  margin: const EdgeInsets.only(top: 7.0),
  child: Row(
    ...
  ),
),

直播聊天彈幕通過Flexible配合Text.rich / TextSpan實現消息自適應佈局界面。

通過 SlideTransition 組件實現直播進場動畫。

return SlideTransition(
  position: animationFirst ? animation : animationMix,
  child: Container(
    alignment: Alignment.centerLeft,
    margin: const EdgeInsets.only(top: 7.0),
    padding: const EdgeInsets.symmetric(horizontal: 7.0,),
    height: 23.0,
    width: 250,
    decoration: const BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.centerLeft,
        end: Alignment.centerRight,
        colors: [
          Color(0xFF6301FF), Colors.transparent
        ],
      ),
      borderRadius: BorderRadius.horizontal(left: Radius.circular(10.0)),
    ),
    child: joinList!.isNotEmpty ? 
      Text('歡迎 ${joinList![0]['name']} 加入直播間', style: const TextStyle(color: Colors.white, fontSize: 14.0,),)
      :
      Container()
    ,
  ),
);
class _AnimationLiveJoinState extends State<AnimationLiveJoin> with TickerProviderStateMixin {
  // 動畫控制器
  late AnimationController controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 500), // 第一個動畫持續時間
  );
  late AnimationController controllerMix = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 1000), // 第二個動畫持續時間
  );
  // 動畫
  late Animation<Offset> animation = Tween(begin: const Offset(2.5, 0), end: const Offset(0, 0)).animate(controller);
  late Animation<Offset> animationMix = Tween(begin: const Offset(0, 0), end: const Offset(-2.5, 0)).animate(controllerMix);

  Timer? timer;
  // 是否第一個動畫
  bool animationFirst = true;
  // 是否空閑
  bool idle = true;
  // 加入直播間數據列表
  List? joinList;

  @override
  void initState() {
    super.initState();

    joinList = widget.joinQueryList!.toList();

    runAnimation();
    animation.addListener(() {
      if(animation.status == AnimationStatus.forward) {
        debugPrint('第一個動畫進行中');
        idle = false;
        setState(() {});
      }else if(animation.status == AnimationStatus.completed) {
        debugPrint('第一個動畫結束');
        animationFirst = false;
        if(controllerMix.isCompleted || controllerMix.isDismissed) {
          timer = Timer(const Duration(seconds: 2), () {
            controllerMix.forward();
            debugPrint('第二個動畫開始');
          });
        }
        setState(() {});
      }
    });
    animationMix.addListener(() {
      if(animationMix.status == AnimationStatus.forward) {
        setState(() {});
      }else if(animationMix.status == AnimationStatus.completed) {
        animationFirst = true;
        controller.reset();
        controllerMix.reset();
        if(joinList!.isNotEmpty) {
          joinList!.removeAt(0);
        }
        idle = true;
        // 執行下一個數據
        runAnimation();
        setState(() {});
      }
    });
  }

  void runAnimation() {
    if(joinList!.isNotEmpty) {
      // 空閑狀態才能執行,防止添加數據播放狀態混淆
      if(idle == true) {
        if(controller.isCompleted || controller.isDismissed) {
          setState(() {});
          timer = Timer(Duration.zero, () {
            controller.forward();
          });
        }
      }
    }
  }

  @override
  void dispose() {
    controller.dispose();
    controllerMix.dispose();
    timer?.cancel();
    super.dispose();
  }

}

綜上就是flutter3實戰開發仿抖音的一些知識分享,由於涉及的技術點還是蠻多的,就沒有非常詳細的展開介紹了。希望以上的一些分享能給大家有些幫助~

最後附上兩個跨端項目

uniapp+vue3短視頻直播商城:https://www.cnblogs.com/xiaoyan2017/p/17938517

tauri+vite4中後臺管理系統:https://www.cnblogs.com/xiaoyan2017/p/17552562

 

本文為博主原創文章,未經博主允許不得轉載,歡迎大家一起交流 QQ(282310962) wx(xy190310)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在GreatSQL中,Binlog可以說是 GreatSQL 中比較重要的日誌了,在日常開發及運維過程中經常會遇到。Binlog即Binary Log,二進位日誌文件,也叫作變更日誌(Update Log)。 詳細Binglog日誌介紹 Binglog主要應用於數據恢復和數據複製,但是在Binlog ...
  • 本文分享自華為雲社區《GaussDB(DWS)業務高可靠原理》,作者: yd_291396996。 1. 前言 適用版本:【8.1.0及以上】 GaussDB(DWS)所有內部組件CN、DN、GTM、CM等採用多活或主備設計,通過集群管理進行故障檢測和切換,保證了單點故障場景下業務的可靠性。此外還採 ...
  • windows 安裝sqlserver服務 SQL Server 下載 | Microsoft 或者MSDN, 我告訴你 - 做一個安靜的工具站 (itellyou.cn) 安裝 關閉windows防火牆並重新運行 一路下一步直到 這樣設置可以讓安裝該服務的用戶直接 安裝管理工具 docker 20 ...
  • 增 增加單條數據 insert into Department(DepartmentName,DepartmentDesc) values('研發部','這是研發部')--插入單條數據 多條 /*insert into [Rank](RankName,RankDesc) select 'A','A級 ...
  • 背景 近年來隨著國際形勢的變化,信創產業成為我國國家戰略的一部分。一直以來,一直以來,全球 ICT 產業底層標準、架構、產品、生態等要素均由國外公司或機構制定和控制,使我國 ICT 產業乃至廣大用戶面臨被卡脖子、數據泄露、信息安全等諸多風險,尤其是 2018年以來,中興、華為等公司的遭遇成為鮮活的實 ...
  • 一、RelativeContainer 1.概述 ArkUI組件中的RelativeContainer是一個相對定位的容器,可以用來將子組件按照相對位置佈局。 在RelativeContainer中,每個子組件都可以設置相對於父容器或其他組件的位置和大小。可以通過設置相對位置、偏移量、寬度和 ...
  • 一、Flex 1.概述 Flex佈局它可以讓容器中的子元素具有彈性伸縮性。Flex佈局是一種二維佈局模型,它可以在任意方向上對元素進行排列,並且可以動態地調整元素的大小和位置,以適應不同的屏幕尺寸和設備。通過使用Flex佈局,我們可以實現響應式佈局效果,以適應不同的屏幕尺寸和設備。Flex布 ...
  • 本文記錄如何使用 adb 命令修改 Android/data 目錄下的文件,然後給國服的碧藍檔案打上布丁~ 前言 今天下午刷著刷著微博就看到國服 BA 又又又發了和諧公告 ... 心情複雜。jpg 於是乎終於想起來得吃布丁了,至於此次更新後布丁有沒有用還未知,但還是先搞上 食用方法之前就出了 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...