主頁 > 後端開發 > FlutterGo 后端知識點提煉:midway+Typescript+mysql(sequelize)

FlutterGo 后端知識點提煉:midway+Typescript+mysql(sequelize)

2020-09-10 10:45:08 後端開發

前言

關于 FlutterGo 或許不用太多介紹了,

如果有第一次聽說的小伙伴,可以移步FlutterGo官網查看下簡單介紹.

FlutterGo 在這次迭代中有了不少的更新,筆者在此次的更新中,負責開發后端以及對應的客戶端部分,這里簡單介紹下關于 FlutterGo 后端代碼中幾個功能模塊的實作,

總體來說,FlutterGo 后端并不復雜,此文中大概介紹以下幾點功能(介面)的實作:

  • FlutterGo 登陸功能
  • 組件獲取功能
  • 收藏功能
  • 建議反饋功能

環境資訊

阿里云 ECS 云服務器

Linux iz2ze3gw3ipdpbha0mstybz 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

mysql :mysql Ver 8.0.16 for Linux on x86_64 (MySQL Community Server - GPL)

node:v12.5.0

開發語言:midway + typescript + mysql

代碼結構:

src
├─ app
│    ├─ class 定義表結構
│    │    ├─ app_config.ts 
│    │    ├─ cat.ts
│    │    ├─ collection.ts
│    │    ├─ user.ts
│    │    ├─ user_collection.ts
│    │    └─ widget.ts
│    ├─ constants 常量
│    │    └─ index.ts
│    ├─ controller 
│    │    ├─ app_config.ts
│    │    ├─ auth.ts
│    │    ├─ auth_collection.ts
│    │    ├─ cat_widget.ts
│    │    ├─ home.ts
│    │    ├─ user.ts
│    │    └─ user_setting.ts
│    ├─ middleware 中間件
│    │    └─ auth_middleware.ts
│    ├─ model
│    │    ├─ app_config.ts
│    │    ├─ cat.ts
│    │    ├─ collection.ts
│    │    ├─ db.ts
│    │    ├─ user.ts
│    │    ├─ user_collection.ts
│    │    └─ widget.ts
│    ├─ public
│    │    └─ README.md
│    ├─ service
│    │    ├─ app_config.ts
│    │    ├─ cat.ts
│    │    ├─ collection.ts
│    │    ├─ user.ts
│    │    ├─ user_collection.ts
│    │    ├─ user_setting.ts
│    │    └─ widget.ts
│    └─ util 工具集
│           └─ index.ts
├─ config 應用的配置資訊
│    ├─ config.default.ts
│    ├─ config.local.ts
│    ├─ config.prod.ts
│    └─ plugin.ts
└─ interface.ts

登陸功能

首先在class/user.ts中定義一個 user 表結構,大概需要的欄位以及在 interface.ts 中宣告相關介面,這里是 midwayts 的基礎配置,就不展開介紹了,

FlutterGo 提供了兩種登陸方式:

  • 用戶名、密碼登陸
  • GitHubOAuth 認證

因為是手機客戶端的 GitHubOauth 認證,所以這里其實是有一些坑的,后面再說,這里我們先從簡單的開始說起

用戶名/密碼登陸

因為我們使用 github 的用戶名/密碼登陸方式,所以這里需要羅列下 github 的 api:developer.github.com/v3/auth/,

檔案中的核心部分:curl -u username https://api.github.com/user (大家可以自行在 terminal 上測驗),回車輸入密碼即可,所以這里我們完全可以在拿到用戶輸入的用戶名和密碼后進行 githu 的認證,

關于 midway 的基本用法,這里也不再贅述了,整個程序還是非常簡單清晰的,如下圖:

相關代碼實作(相關資訊已脫敏:xxx):

service部分

    //獲取 userModel
    @inject()
    userModel
    
    // 獲取 github 配置資訊
    @config('githubConfig')
    GITHUB_CONFIG;

    //獲取請求背景關系
    @inject()
    ctx;
    //githubAuth 認證
    async githubAuth(username: string, password: string, ctx): Promise<any> {
        return await ctx.curl(GITHUB_OAUTH_API, {
            type: 'GET',
            dataType: 'json',
            url: GITHUB_OAUTH_API,
            headers: {
                'Authorization': ctx.session.xxx
            }
        });
    }
    // 查找用戶 
    async find(options: IUserOptions): Promise<IUserResult> {
        const result = await this.userModel.findOne(
            {
                attributes: ['xx', 'xx', 'xx', 'xx', 'xx', "xx"],//相關資訊脫敏
                where: { username: options.username, password: options.password }
            })
            .then(userModel => {
                if (userModel) {
                    return userModel.get({ plain: true });
                }
                return userModel;
            });
        return result;
    }
    // 通過 URLName 查找用戶
    async findByUrlName(urlName: string): Promise<IUserResult> {
        return await this.userModel.findOne(
            {
                attributes: ['xxx', 'xxx', 'xxx', 'xxx', 'xxx', "xxx"],
                where: { url_name: urlName }
            }
        ).then(userModel => {
            if (userModel) {
                return userModel.get({ plain: true });
            }
            return userModel;
        });
    }
    // 創建用戶
    async create(options: IUser): Promise<any> {
        const result = await this.userModel.create(options);
        return result;
    }
    
    // 更新用戶資訊
    async update(id: number, options: IUserOptions): Promise<any> {
        return await this.userModel.update(
            {
                username: options.username,
                password: options.password
            },
            {
                where: { id },
                plain: true
            }
        ).then(([result]) => {
            return result;
        });
    }

controller

    // inject 獲取 service 和加密字串
    @inject('userService')
    service: IUserService

    @config('random_encrypt')
    RANDOM_STR;
流程圖中邏輯的代碼實作

GitHubOAuth 認證

這里有坑!我回頭介紹

githubOAuth 認證就是我們常說的 github app 了,這里我直接了當的丟檔案:creating-a-github-app


筆者還是覺得檔案類的無需介紹

當然,我這里肯定都建好了,然后把一些基本資訊都寫到 server 端的配置中

還是按照上面的套路,咱們先介紹流程,然后在說坑在哪,

客戶端部分

客戶端部分的代碼就相當簡單了,新開 webView ,直接跳轉到 github.com/login/oauth/authorize 帶上 client_id即可,

server 端

整體流程如上,部分代碼展示:

service

    //獲取 github access_token
    async getOAuthToken(code: string): Promise<any> {
        return await this.ctx.curl(GITHUB_TOKEN_URL, {
            type: "POST",
            dataType: "json",
            data: {
                code,
                client_id: this.GITHUB_CONFIG.client_id,
                client_secret: this.GITHUB_CONFIG.client_secret
            }
        });
    }

controller代碼邏輯就是呼叫 service 中的資料來走上面流程圖中的資訊,

OAuth 中的坑

其實,github app 的認證方式非常適用于瀏覽器環境下,但是在 flutter 中,由于我們是新開啟的 webView 來請求的 github 登陸地址,當我們后端成功回傳的時候,無法通知到 Flutter 層,就導致我自己的 Flutter 中 dart 寫的代碼,無法拿到介面的回傳,

中間腦暴了很多解決辦法,最終在查閱 flutter_webview_plugin 的 API 里面找了個好的方法:onUrlChanged

簡而言之就是,Flutter 客戶端部分新開一個 webView去請求 github.com/login,github.com/login檢查 client_id 后會帶著code 等亂七八糟的東西來到后端,后端校驗成功后,redirect Flutter 新開的 webView,然后flutter_webview_plugin去監聽頁面 url 的變化,發送相關 event ,讓Flutter 去 destroy 當前 webVIew,處理剩余邏輯,

Flutter 部分代碼

//定義相關 OAuth event
class UserGithubOAuthEvent{
  final String loginName;
  final String token;
  final bool isSuccess;
  UserGithubOAuthEvent(this.loginName,this.token,this.isSuccess);
}

webView page:

    //在 initState 中監聽 url 變化,并emit event
    flutterWebviewPlugin.onUrlChanged.listen((String url) {
      if (url.indexOf('loginSuccess') > -1) {
        String urlQuery = url.substring(url.indexOf('?') + 1);
        String loginName, token;
        List<String> queryList = urlQuery.split('&');
        for (int i = 0; i < queryList.length; i++) {
          String queryNote = queryList[i];
          int eqIndex = queryNote.indexOf('=');
          if (queryNote.substring(0, eqIndex) == 'loginName') {
            loginName = queryNote.substring(eqIndex + 1);
          }
          if (queryNote.substring(0, eqIndex) == 'accessToken') {
            token = queryNote.substring(eqIndex + 1);
          }
        }
        if (ApplicationEvent.event != null) {
          ApplicationEvent.event
              .fire(UserGithubOAuthEvent(loginName, token, true));
        }
        print('ready close');

        flutterWebviewPlugin.close();
        // 驗證成功
      } else if (url.indexOf('${Api.BASE_URL}loginFail') == 0) {
        // 驗證失敗
        if (ApplicationEvent.event != null) {
          ApplicationEvent.event.fire(UserGithubOAuthEvent('', '', true));
        }
        flutterWebviewPlugin.close();
      }
    });

login page:

    //event 的監聽、頁面跳轉以及提醒資訊的處理
    ApplicationEvent.event.on<UserGithubOAuthEvent>().listen((event) {
      if (event.isSuccess == true) {
        //  oAuth 認證成功
        if (this.mounted) {
          setState(() {
            isLoading = true;
          });
        }
        DataUtils.getUserInfo(
                {'loginName': event.loginName, 'token': event.token})
            .then((result) {
          setState(() {
            isLoading = false;
          });
          Navigator.of(context).pushAndRemoveUntil(
              MaterialPageRoute(builder: (context) => AppPage(result)),
              (route) => route == null);
        }).catchError((onError) {
          print('獲取身份資訊 error:::$onError');
          setState(() {
            isLoading = false;
          });
        });
      } else {
        Fluttertoast.showToast(
            msg: '驗證失敗',
            toastLength: Toast.LENGTH_SHORT,
            gravity: ToastGravity.CENTER,
            timeInSecForIos: 1,
            backgroundColor: Theme.of(context).primaryColor,
            textColor: Colors.white,
            fontSize: 16.0);
      }
    });

組件樹獲取

表結構

在聊介面實作的之前,我們先了解下,關于組件,我們的表機構設計大概是什么樣子的,

FlutterGO 下面 widget tab很多分類,分類點進去還是分類,再點擊去是組件,組件點進去是詳情頁,


上圖模塊點進去就是組件 widget


上圖是 widget,點進去是詳情頁

所以這里我們需要兩張表來記錄他們的關系:cat(category)和 widget 表,

cat 表中我們每行資料會有一個 parent_id 欄位,所以表記憶體在父子關系,而 widget 表中的每一行資料的 parent_id 欄位的值必然是 cat 表中的最后一層,比如 Checkbox widgetparent_id 的值就是 cat 表中 Button 的 id,

需求實作

在登陸的時候,我們希望能獲取所有的組件樹,需求方要求結構如下:

[
   {
    "name": "Element",
      "type": "root",
      "child": [
        {
          "name": "Form",
            "type": "group",
            "child": [
              {
                "name": "input",
                  "type": "page",
                  "display": "old",
                  "extends": {},
                  "router": "/components/Tab/Tab"
               },
               {
                "name": "input",
                  "type": "page",
                  "display": "standard",
                  "extends": {},
                  "pageId": "page1_hanxu_172ba42f_0520_401e_b568_ba7f7f6835e4"
               }
            ]
         }
      ],
   }
]

因為現在存在三方共建組件,而且我們詳情頁也較FlutterGo 1.0 版本有了很大改動,如今組件的詳情頁只有一個,內容全部靠 md 渲染,在 md 中寫組件的 demo 實作,所以為了兼容舊版本的 widget,我們有 display 來區分,新舊 widget 分別通過 pageIdrouter 來跳轉頁面,

新建 widget 的 pageId 是通過FlutterGo 腳手架 goCli生成的

目前實作實際回傳為:

{
    "success": true,
    "data": [
        {
            "id": "3",
            "name": "Element",
            "parentId": 0,
            "type": "root",
            "children": [
                {
                    "id": "6",
                    "name": "Form",
                    "parentId": 3,
                    "type": "category",
                    "children": [
                        {
                            "id": "9",
                            "name": "Input",
                            "parentId": 6,
                            "type": "category",
                            "children": [
                                {
                                    "id": "2",
                                    "name": "TextField",
                                    "parentId": "9",
                                    "type": "widget",
                                    "display": "old",
                                    "path": "/Element/Form/Input/TextField"
                                }
                            ]
                        },
                        {
                            "id": "12",
                            "name": "Text",
                            "parentId": 6,
                            "type": "category",
                            "children": [
                                {
                                    "id": "3",
                                    "name": "Text",
                                    "parentId": "12",
                                    "type": "widget",
                                    "display": "old",
                                    "path": "/Element/Form/Text/Text"
                                },
                                {
                                    "id": "4",
                                    "name": "RichText",
                                    "parentId": "12",
                                    "type": "widget",
                                    "display": "old",
                                    "path": "/Element/Form/Text/RichText"
                                }
                            ]
                        },
                        {
                            "id": "13",
                            "name": "Radio",
                            "parentId": 6,
                            "type": "category",
                            "children": [
                                {
                                    "id": "5",
                                    "name": "TestNealya",
                                    "parentId": "13",
                                    "type": "widget",
                                    "display": "standard",
                                    "pageId": "page1_hanxu_172ba42f_0520_401e_b568_ba7f7f6835e4"
                                }
                            ]
                        }
                    ]
                }
            ]
        }
        {
            "id": "5",
            "name": "Themes",
            "parentId": 0,
            "type": "root",
            "children": []
        }
    ]
}

簡單示例,省去 99%資料

代碼實作

其實這個介面也是非常簡單的,就是個雙回圈遍歷嘛,準確的說,有點類似深度優先遍歷,直接看代碼吧

獲取所有 parentId 相同的 category (后面簡稱為 cat)

async getAllNodeByParentIds(parentId?: number) {
    if (!!!parentId) {
        parentId = 0;
    }

    return await this.catService.getCategoryByPId(parentId);
}

首字母轉小寫

firstLowerCase(str){
    return str[0].toLowerCase()+str.slice(1);
}

我們只要自己外部維護一個組件樹,然后cat表中的讀取到的每一個parent_id都是一個節點,當前 id 沒有別的 cat 對應的 parent_id就說明它的下一級是“葉子” widget了,所以就從 widget 中查詢即可,easy~

    //洗掉部分不用代碼
   @get('/xxx')
    async getCateList(ctx) {
        const resultList: IReturnCateNode[] = [];
        let buidList = async (parentId: number, containerList: Partial<IReturnCateNode>[] | Partial<IReturnWidgetNode>[], path: string) => {
            let list: IReturnCateNode[] = await this.getAllNodeByParentIds(parentId);
            if (list.length > 0) {
                for (let i = 0; i < list.length; i++) {
                    let catNode: IReturnCateNode;
                    catNode = {
                        xxx:xxx
                    }
                    containerList.push(catNode);
                    await buidList(list[i].id, containerList[i].children, `${path}/${this.firstLowerCase(containerList[i].name)}`);
                }
            } else {
                // 沒有 cat 表下 children,判斷是否存在 widget
                const widgetResult = await this.widgetService.getWidgetByPId(parentId);
                if (widgetResult.length > 0) {
                    widgetResult.map((instance) => {
                        let tempWidgetNode: Partial<IReturnWidgetNode> = {};
                        tempWidgetNode.xxx = instance.xxx;
                        if (instance.display === 'old') {
                            tempWidgetNode.path = `${path}/${this.firstLowerCase(instance.name)}`;
                        } else {
                            tempWidgetNode.pageId = instance.pageId;
                        }
                        containerList.push(tempWidgetNode);
                    });
                } else {
                    return null;
                }

            }
        }
        await buidList(0, resultList, '');
        ctx.body = { success: true, data: resultList, status: 200 };
    }

彩蛋

FlutterGo 中有一個組件搜索功能,因為我們存盤 widget 的時候,并沒有強制帶上該 widget的路由,這樣也不合理(針對于舊組件),所以在widget表中搜索出來,還要像上述程序那樣逆向搜索獲取“舊”widgetrouter欄位

我的個人代碼實作大致如下:

    @get('/xxx')
    async searchWidget(ctx){
        let {name} = ctx.query;
        name = name.trim();
        if(name){
            let resultWidgetList = await this.widgetService.searchWidgetByStr(name);
            if(xxx){
                for(xxx){
                    if(xxx){
                        let flag = true;
                        xxx
                        while(xxx){
                            let catResult = xxx;
                            if(xxx){
                               xxx
                                if(xxx){
                                    flag = false;
                                }
                            }else{
                                flag = false;
                            }
                        }
                        resultWidgetList[i].path = path;
                    }
                }
                ctx.body={success:true,data:resultWidgetList,message:'查詢成功'};
            }else{
                ctx.body={success:true,data:[],message:'查詢成功'};
            }
        }else{
            ctx.body={success:false,data:[],message:'查詢欄位不能為空'};
        }
        
    }

求大神指教最簡實作~??

收藏功能

收藏功能,必然是跟用戶掛鉤的,然后收藏的組件該如何跟用戶掛鉤呢?組件跟用戶是多對多的關系,

這里我新建一個collection表來用作所有收藏過的組件,為什么不直接使用widget表呢,因為我個人不希望表太過于復雜,無用的欄位太多,且功能不單一,

由于是收藏的組件和用戶是多對多的關系,所以這里我們需要一個中間表user_collection來維護他兩的關系,三者關系如下:

功能實作思路

  • 校驗收藏

    • collection表中檢查用戶傳入的組件資訊,沒有則為收藏、有則取出其在 collection 表中的 id
    • session 中獲取用戶的 id
    • collection_iduser_id 來檢索user_collection表中是否有這個欄位
  • 添加收藏

    • 獲取用戶傳來的組件資訊
    • findOrCrate的檢索 collection表,并且回傳一個 collection_id
    • 然后將 user_idcollection_id存入到 user_collection 表中(互不信任原則,校驗下存在性)
  • 移除收藏

    • 步驟如上,拿到 collection 表中的 collection_id
    • 洗掉 user_collection 對應欄位即可
  • 獲取全部收藏

    • 檢索 collection 表中所有 user_id 為當前用戶的所有 collection_id
    • 通過拿到的collection_ids 來獲取收藏的組件串列

部分代碼實作

整體來說,思路還是非常清晰的,所以這里我們僅僅拿收藏和校驗來展示下部分代碼:

service層代碼實作

    @inject()
    userCollectionModel;
        async add(params: IuserCollection): Promise<IuserCollection> {
        return await this.userCollectionModel.findOrCreate({
            where: {
                user_id: params.user_id, collection_id: params.collection_id
            }
        }).then(([model, created]) => {
            return model.get({ plain: true })
        })
    }

    async checkCollected(params: IuserCollection): Promise<boolean> {
        return await this.userCollectionModel.findAll({
            where: { user_id: params.user_id, collection_id: params.collection_id }
        }).then(instanceList => instanceList.length > 0);
    }

controller層代碼實作

    @inject('collectionService')
    collectionService: ICollectionService;

    @inject()
    userCollectionService: IuserCollectionService

    @inject()
    ctx;
    
    // 校驗組件是否收藏
    @post('/xxx')
    async checkCollected(ctx) {
        if (ctx.session.userInfo) {
            // 已登錄
            const collectionId = await this.getCollectionId(ctx.request.body);
            const userCollection: IuserCollection = {
                user_id: this.ctx.session.userInfo.id,
                collection_id: collectionId
            }
            const hasCollected = await this.userCollectionService.checkCollected(userCollection);
            ctx.body={status:200,success:true,hasCollected};

        } else {
            ctx.body={status:200,success:true,hasCollected:false};
        }
    }
    
    async addCollection(requestBody): Promise<IuserCollection> {

        const collectionId = await this.getCollectionId(requestBody);

        const userCollection: IuserCollection = {
            user_id: this.ctx.session.userInfo.id,
            collection_id: collectionId
        }

        return await this.userCollectionService.add(userCollection);
    }

因為常要獲取 collection 表中的 collection_id 欄位,所以這里抽離出來作為公共方法

    async getCollectionId(requestBody): Promise<number> {
        const { url, type, name } = requestBody;
        const collectionOptions: ICollectionOptions = {
            url, type, name
        };
        const collectionResult: ICollection = await this.collectionService.findOrCreate(collectionOptions);
        return collectionResult.id;
    }

feedback 功能

feedback 功能就是直接可以在 FlutterGo 的個人設定中,發送 issue 到 Alibaba/flutter-go 下,這里主要也是呼叫 github 的提 issue 介面 api issues API,

后端的代碼實作非常簡單,就是拿到資料,呼叫 github 的 api 即可

service

    @inject()
    ctx;

    async feedback(title: string, body: string): Promise<any> {
        return await this.ctx.curl(GIHTUB_ADD_ISSUE, {
            type: "POST",
            dataType: "json",
            headers: {
                'Authorization': this.ctx.session.headerAuth,
            },
            data: JSON.stringify({
                title,
                body,
            })
        });
    }

controller

    @inject('userSettingService')
    settingService: IUserSettingService;

    @inject()
    ctx;

    async feedback(title: string, body: string): Promise<any> {
        return await this.settingService.feedback(title, body);
    }

彩蛋

猜測可能會有人 FlutterGo 里面這個 feedback 是用的哪一個組件~這里介紹下

pubspec.yaml

  zefyr:
    path: ./zefyr

因為在開發的時候,flutter 更新了,導致zefyr 運行報錯,當時也是提了 issue:chould not Launch FIle (寫這篇文章的時候才看到回復)

但是當時由于功能開發要發布,等了好久沒有zefyr作者的回復,就在本地修復了這個 bug,然后包就直接引入本地的包了,

共建計劃

咳咳,敲黑板啦~~

Flutter 依舊在不斷地更新,但僅憑我們幾個 Flutter 愛好者在作業之余維護 FlutterGo 還是非常吃力的,所以這里,誠邀業界所有 Flutter 愛好者一起參與共建 FlutterGo!

此處再次感謝所有已經提交 pr 的小伙伴

共建說明

由于 Flutter 版本迭代速度較快,產生的內容較多, 而我們人力有限無法更加全面快速的支持Flutter Go的日常維護迭代, 如果您對flutter go的共建感興趣, 歡迎您來參與本專案的共建.

凡是參與共建的成員. 我們會將您的頭像與github個人地址收納進我們的官方網站中.

共建方式

  1. 共建組件
  • 本次更新, 開放了 Widget 內容收錄 的功能, 您需要通過 goCli 工具, 創建標準化組件,撰寫markdown代碼,

  • 為了更好記錄您的改動目的, 內容資訊, 交流程序, 每一條PR都需要對應一條 Issue, 提交你發現的BUG或者想增加的新功能, 或者想要增加新的共建組件,

  • 首先選擇你的issue在型別,然后通過 Pull Request 的形式將文章內容, api描述, 組件使用方法等加入進我們的Widget界面,

  1. 提交文章和修改bug
  • 您也可以將例如日常bug. 未來feature等的功能性PR, 申請提交到我們的的主倉庫,

參與共建

關于如何提PR請先閱讀以下檔案

  • 如何向倉庫提交 Pull Request
  • dart 代碼規范
  • 如何使用go-cli 創建 Widget Page

貢獻指南

此專案遵循貢獻者行為準則,參與此專案即表示您同意遵守其條款.

FlutterGo 期待你我共建~

具體 pr 細節和流程可參看 FlutterGo README 或 直接釘釘掃碼入群

學習交流

關注公眾號: 【全堆疊前端精選】 每榷訓取好文推薦,

公眾號內回復 【1】,加入全堆疊前端學習群,一起交流,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/1873.html

標籤:Dart

上一篇:dart 大檔案讀取

下一篇:C++分治策略實作二分搜索

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more