分類
發燒車訊

angular 接入 IdentityServer4

angular 接入 IdentityServer4

Intro

最近把活動室預約的項目做了一個升級,預約活動室需要登錄才能預約,並用 IdentityServer4 做了一個統一的登錄註冊中心,這樣以後就可以把其他的需要用戶操作的應用統一到 IdentityServer 這裏,這樣就不需要在每個應用里都做一套用戶的機制,接入 IdentityServer 就可以了。

目前活動室預約的服務器端和基於 angular 的客戶端已經完成了 IdentityServer 的接入,並增加了用戶的相關的一些功能,比如用戶可以查看自己的預約記錄並且可以取消自己未開始的預約,

還有一個小程序版的客戶端暫時還未完成接入,所以小程序版目前暫時是不能夠預約的

為什麼要寫這篇文章

目前在網上看到很多都是基於 implicit 模式接入 IdentityServer,這樣實現起來很簡單,但是現在 OAuth 已經不推薦這樣做了,OAuth 推薦使用 code 模式來代替 implicit

implicit 模式會有一些安全風險,implicit 模式會將 accessToken 直接返回到客戶端,而 code 模式只是會返回一個 code,accessToken 和 code 的分離的兩步,implicit 模式很有可能會將 token 泄露出去

詳細可以參考 StackOverflow 上的這個問答

https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works

除此之外,還有一個小原因,大多是直接基於 oidc-client 的 一個 npm 包來實現的,我是用了一個針對 angular 封裝的一個庫 angular-oauth2-oidc,如果你在用 angular ,建議你可以嘗試一下,針對 angular 做了一些封裝和優化,對 angular 更友好一些

準備接入吧

API 配置

預約系統的 API 和網站管理系統是在一起的,針對需要登錄才能訪問的 API 單獨設置了的 policy 訪問

services.AddAuthentication()
    .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.Authority = Configuration["Authorization:Authority"];
        options.RequireHttpsMetadata = false;

        options.NameClaimType = "name";
        options.RoleClaimType = "role";
    })
    ;

services.AddAuthorization(options =>
{
    options.AddPolicy("ReservationApi", builder => builder
        .AddAuthenticationSchemes(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .RequireScope("ReservationApi")
    );
});

需要授權才能訪問的接口設置 Authorize 並指定 Policy 為 ReservationApi

[Authorize(Policy = "ReservationApi")]
[HttpPost]
public async Task<IActionResult> MakeReservation([FromBody] ReservationViewModel model)

IdentityServer Client 配置

首先我們需要在 IdentityServer 這邊添加一個客戶端,因為我們要使用 code 模式,所以授權類型需要配置 authorization-code 模式,不使用 implicit 模式

允許的作用域(scope) 是客戶端允許訪問的 api 資源和用戶的信息資源,openid 必選,profile 是默認的用戶基本信息的集合,根據自己客戶端的需要進行配置,ReservationApi 是訪問 API 需要的 scope,其他的 scope 根據客戶端需要進行配置

angular 客戶端配置

安裝 angular-oauth2-oidc npm 包,我現在使用的是 9.2.0 版本

添加 oidc 配置:

export const authCodeFlowConfig: AuthConfig = {
  issuer: 'https://id.weihanli.xyz',

  // URL of the SPA to redirect the user to after login
  redirectUri: window.location.origin + '/account/callback',

  clientId: 'reservation-angular-client',

  dummyClientSecret: 'f6f1f917-0899-ef36-63c8-84728f411e7c',

  responseType: 'code',

  scope: 'openid profile ReservationApi offline_access',

  useSilentRefresh: false,

  showDebugInformation: true,

  sessionChecksEnabled: true,

  timeoutFactor: 0.01,

  // disablePKCI: true,

  clearHashAfterLogin: false
};

在 app.module 引入 oauth 配置

  imports: [
    BrowserModule,
    AppRoutingModule,
    AppMaterialModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
    BrowserAnimationsModule,
    OAuthModule.forRoot({
      resourceServer: {
        allowedUrls: ['https://reservation.weihanli.xyz/api'],
        sendAccessToken: true
      }
    })
  ]

OAuthModule 里 resourceServer 中的 allowedUrls 是配置的資源的地址,訪問的資源符合這個地址時就會自動發送 accessToken,這樣就不需要自己實現一個 interceptor 來實現自動在請求頭中設置 accessToken 了

在 AppComponment 的構造器中初始化 oauth 配置,並加載 ids 的發現文檔

export class AppComponent {
  constructor(
        private oauth: OAuthService
    ) {
    this.oauth.configure(authConfig.authCodeFlowConfig);
    this.oauth.loadDiscoveryDocument();
    }
    // ...
}

添加一個 AuthGuard,路由守衛,需要登錄才能訪問的頁面自動跳轉到 /account/login 自動登錄

AuthGuard:

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router, private oauthService: OAuthService) {}

  canActivate() {
    if (this.oauthService.hasValidAccessToken()) {
      return true;
    } else {
      this.router.navigate(['/account/login']);
      return false;
    }
  }
}

路由配置:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ReservationListComponent } from './reservation/reservation-list/reservation-list.component';
import { NoticeListComponent } from './notice/notice-list/notice-list.component';
import { NoticeDetailComponent } from './notice/notice-detail/notice-detail.component';
import { AboutComponent } from './about/about.component';
import { NewReservationComponent } from './reservation/new-reservation/new-reservation.component';
import { LoginComponent } from './account/login/login.component';
import { AuthGuard } from './shared/auth.guard';
import { AuthCallbackComponent } from './account/auth-callback/auth-callback.component';
import { MyReservationComponent } from './account/my-reservation/my-reservation.component';

const routes: Routes = [
  { path: '', component: ReservationListComponent },
  { path: 'reservations/new', component:NewReservationComponent, canActivate: [AuthGuard] },
  { path: 'reservations', component: ReservationListComponent },
  { path: 'notice', component: NoticeListComponent },
  { path: 'notice/:noticePath', component: NoticeDetailComponent },
  { path: 'about', component: AboutComponent },
  { path: 'account/login', component: LoginComponent },
  { path: 'account/callback', component: AuthCallbackComponent },
  { path: 'account/reservations', component: MyReservationComponent, canActivate: [AuthGuard] },
  { path: '**', redirectTo: '/'}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

AccountLogin 會將用戶引導到 ids 進行登錄,登錄之後會跳轉到配置的重定向 url,我配置的是 account/callback

import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {

  constructor(private oauthService: OAuthService) {
  }

  ngOnInit(): void {
    // 登錄
    this.oauthService.initLoginFlow();
  }

}

Auth-Callback

import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { Router } from '@angular/router';

@Component({
  selector: 'app-auth-callback',
  templateUrl: './auth-callback.component.html',
  styleUrls: ['./auth-callback.component.less']
})
export class AuthCallbackComponent implements OnInit {

  constructor(private oauthService: OAuthService, private router:Router) {
  }

  ngOnInit(): void {
    this.oauthService.loadDiscoveryDocumentAndTryLogin()
    .then(_=> {
      this.oauthService.loadUserProfile().then(x=>{
        this.router.navigate(['/reservations/new']);
      });
    });
  }

}

More

當前實現還不太完善,重定向現在始終是跳轉到的新預約的頁面,應當在跳轉登錄之前記錄一下當前的地址保存在 storage 中,在 auth-callback 里登錄成功之後跳轉到 storage 中之前的地址

Reference

  • https://sunnycoding.cn/2020/03/14/angular-spa-auth-with-ocelot-and-ids4-part3/#i-2
  • https://github.com/OpenReservation/angular-client
  • https://github.com/manfredsteyer/angular-oauth2-oidc/
  • https://github.com/OpenReservation/ReservationServer

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

萬向李澤楷競買菲斯科 今日將出結果

據《華爾街日報》報導,美國電動汽車品牌菲斯科(Fisker)最終將於美國當地時間2月12日在紐約進行拍賣,美國法院表示會在一個工作日後宣判結果,李澤楷控股的混合動力技術控股有限公司與萬向集團仍是最有可能的競買成功者。

據路透社的消息,混合動力為加強此次競買工作以及公司的管理,特別聘請了曾主要負責福特歐洲業務的前高管Martin Leach。Leach表示,混合動力目前最大的困境是,萬向集團在一年前收購了A123系統公司,而A123是Fisker的主要電池供應商,不過混合動力可以得到另一家電池公司波士頓動力的支持。

美國汽車經銷協會首席經濟學家史蒂文表示,競買的最終贏家將獲得的不僅是Fisker的汽車產品或設計,還包括其知識產權,其中涉及的36項專利(大約一半為待定),包括電氣傳動系統、太陽能等專利技術。

據悉,萬向錢潮作為萬向集團控股的汽車零部件製造和銷售公司,其股價近日來也因受各種消息刺激連續上漲。

萬向錢潮並於昨(12)日發佈公告表示,從2000年開展電動汽車研發以來,控股股東萬向集團一直致力於發展清潔能源產業,但與國際先進技術相比尚有一定差距。為此,萬向集團希望通過併購聯合方式提升技術能力等。此前,萬向集團掌門人魯冠球曾表示,對同特斯拉電動車結盟合作抱開放態度。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

北汽集團將成為美國新能源公司Atieva第一大股東

北京汽車集團有限公司(北汽集團)昨(17)日宣布,與美國新能源公司Atieva簽署股份認購協議,北汽集團將收購Atieva公司25.02%的股份。收購完成後,北汽將成為Atieva的第一大股東,雙方預計在第3年推出與奧迪A6L同等級的電動汽車。

由於美國電動車大廠特斯拉也正和北汽股份洽談合作事項,昨日北汽旗下的上市公司福田汽車,股價也跟著漲停。

去年,北汽集團新成立了新能源汽車公司,而此次收購的美國Atieva公司是一家新能源汽車核心系統提供商,曾主要參與過Tesla Roadster純電動跑車、雪佛蘭Volt插電式混合電動車、奧迪R8純電動跑車的開發。

北汽集團方面也表示,此次收購主要是為進一步提升北汽集團及下屬公司在新能源汽車尤其是高端純電動汽車領域的設計、研發和制造的能力和水平。

據悉,北汽由6家股東組成,除北汽集團以51%的股比成為控股股東外,首鋼股份有限公司以18.31%的股比成為第二大股東,其他股東包括北京市國資公司、現代創新控股公司及京能集團,而北京市國資委直屬的投融資平台-北京國有資本經營管理中心也持股5%。

另據外媒稍早報導,北汽可望在2014年第2季在香港IPO上市,籌資額度或達到20億美元。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

第二屆新能源車與充電技術中國國際論壇

地點:上海綠地萬豪酒店 /中國上海黃浦區江濱路99號(靠近打浦路)
會議背景:

    新能源汽車是國務院確定的重要戰略性新興產業,是我國最具發展潛力的重要領域之一。近日來中國大面積飽受霧霾天氣困擾的嚴峻形勢,更使得加快新能源汽車的發展迫在眉睫。隨著新一輪新能源車補貼政策的出臺以及首批28個示範運行城市的公佈,新能源汽車產業展現出良好的發展態勢。

    中國政府和車企致力於電動車在全國範圍內的大規模推廣。工信部發佈,我國2015年應有規模將達到50萬輛,到2020年將達到500萬輛。據專家預測,中國十年內將有望成為最大的新能源車市場。為適應電動汽車發展要求,國家將在“十二五”期間,建設充換電站2351座,充電樁22萬股,初步建成覆蓋公司經營區域的智慧充換電服務網路。與此同時,行業在積累了一些寶貴的經驗的同時也出現了在技術路徑、政策導向、基礎設施、商業模式、行業標準等方面急需解決的一些突出問題。

    比亞迪,奇瑞,大眾,通用,富豪等車企都在為滿足新一輪中國客戶對新能源汽車的需求謀劃新局,為更好的享受政府補貼而將新能源汽車技術和生產實現本土化。

  由全國電力技術市場協會機構指導,OPPLAND MEDIA策劃組織的第二屆新能源車與充電技術中國國際論壇將於2014年4月29-30日在上海舉辦。大會網站:

    第二屆新能源車與充電技術中國國際論壇2014旨在加快新能源車推廣,促進中國電動車產業發展,進一步加強行業交流,深入探討技術創新,共同分享中國現有電動車示範城市專案運營經驗,為中國新能源車發展及環境治理出一份力。

    論壇將圍繞“清除環境與政策上的霧霾”為主題,就新能源汽車關鍵技術發展與應用,產業創新研發機制,示範運營與市場推廣、基礎配套設施建設、國際合作與交流等一系列熱點問題展開廣泛地探討與合作,推動新能源汽車產業化、市場化、國際化。本次論壇將邀請國家發展改革委等國家有關部門領導,歐洲汽車工業協會,美國加州大學、美國電動車聯盟等國際組織、中國汽車工業協會、中國汽車工程學會、中國汽車技術研究中心、清華大學、同濟大學等行業組織和研究機構的專家學者,美國加州,挪威、倫敦、北京、上海、天津、廣州、深圳等中外地方政府官員,以及國內外各大汽車集團高層,共同圍繞節能與新能源汽車產業發展的宏觀視角、政策與管理、戰略目標與方向、汽車企業的節能與新能源戰略與行動、新能源汽車發展中的新競爭以及商業探索與經驗交流等議題進行深入研討和交流。

會議板塊:

1. 中國新能源車政策和市場發展
2. 建立中國新能源車標準化體系規範之路
3. 新能源車電池和電機控制系統科技創新
4. 中國電動車充電基礎設施建設新進展

會議熱點:
中國發展新能源車市場應用的路線圖
中國新能源車市場的投資計畫以及帶給全球OEM的機會
中國新能源車財政補貼從地方政府到車企的政策更新
下一輪新能源車推廣應用項目概覽
純電動車,混動車等新能源車技術交流
下一代新能源車新電池組件和關鍵技術
中國充電基礎設施和車企之間的協調發展
BMS電池管理系統迎合中國新能源車增長的精細化管理
新能源車快速發展的產業需求和規劃
統一存在于車企、政府和供應商之間的不同標準

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

日企推水陸兩用電動汽車FOMM Concept One

目前限制全球電動汽車發展的主要瓶頸,在於電池成本和續航力問題。日前有日本廠商推出一款名為FOMM Concept One的超小型電動車,雖說續航力僅有100km,但是卻有另外一點吸引人的地方,那就是如果碰到水災還能變成小艇,且價格非常親民。據說不到日幣100萬圓(約合新台幣29.7萬元)。

這款小型電動汽車,雖然不是專門的水陸兩用車,但遇到洪水等緊急情況,可以在水面漂浮24小時,也能在水面上以時速3.8公里左右的速度移動。車內裝有可拆卸的電池,充滿電後最多可在陸地上行駛100公里。這款4人座汽車全長約2.5公尺,重量僅460公斤,計劃從明年10月開始先在水災較多的泰國銷售。

FOMM公司由日本大同工業(DAIDO Kogyo)、日本特殊陶業(NGK)所共同建立,FOMM Concept One為幾家公司合作下的第一個產物。根據FOMM的規劃, Concept One的開發成本不低,但2014年4-6月將有一比龐大資金注入,倘若一切都順利預計2015年9月將可在泰國進行量產,至於第一年銷售量目標是5000輛。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

將電動車行程提升超10% 英公司開發MSYS動力系統

據英國媒體報導,英國動力系統設計公司宣佈,該公司正在開發一套名為MSYS的電動汽車動力系統,可以將電動汽車行駛里程增加10%至15%。MSYS系統預計將於2016年投產。

在英國即將舉辦的未來動力系統會議上,英國動力系統設計公司將發佈高效電動汽車動力系統報告。該動力系統提供了一種新的汽車換擋路徑,而且不會引起扭矩中斷。

該公司技術總監阿萊克斯•泰利•博達爾表示,該技術可提供55千瓦的持續電力供應,超過2000牛/米的扭矩,其電力動力系統效率可達91%。

阿萊克斯還透露,MSYS系統避免了其他傳動方法的弊端。雙離合器變速箱(DCT)在離合器開啟或關閉狀態時,都會持續消耗能量。而在換擋時,自動變速器(AMT)會受損於扭矩中斷。此外,基於行星齒輪變速器的自動裝置,則增加了複雜性和成本,並會拖慢速度。

MSYS系統將提供三種速度傳動裝置,使其換擋和快速換擋都能運用重疊換擋技術。隨著技術的完善,多重比例的選擇對電動汽車的變速會非常有益。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

LeetCode 79,這道走迷宮問題為什麼不能用寬搜呢?

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是LeetCode專題第48篇文章,我們一起來看看LeetCode當中的第79題,搜索單詞(Word Search)。

這一題官方給的難度是Medium,通過率是34.5%,點贊3488,反對170。單從這份數據上來看,這題的質量很高,並且難度比之前的題目稍稍大一些。我個人覺得通過率是比官方給的題目難得更有參考意義的指標,10%到20%可以認為是較難的題,30%左右是偏難的題。50%是偏易題,所以如果看到某題標着Hard,但是通過率有50%,要麼說明題目很水,要麼說明數據很水,總有一點很水。

題意

廢話不多說,我們來看題意:

這題的題面挺有意思,給定一個二維的字符型數組,以及一個字符串,要求我們來判斷能否在二維數組當中找到一條路徑,使得這條路徑上的字符連成的字符串和給定的字符串相等?

樣例

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

Given word = "ABCCED", return true.
Given word = "SEE", return true.
Given word = "ABCB", return false.

比如第一個字符串ABCCED,我們可以在數組當中找到這樣一條路徑:

題解

不知道大家看到題面和這個樣例有什麼樣的感覺,如果你刷過許多題,經常思考的話,我想應該不難發現,這道題的本質其實和走迷宮問題是一樣的。

我們拿到的這個二維的字符型數組就是一個迷宮, 我們是要在這個迷宮當中找一條“出路”。不過我們的目的不是找到終點,而是找到一條符合題意的路徑。在走迷宮問題當中,迷宮中不是每一個點都可以走的,同樣在當前問題當中,也不是每一個點都符合字符串的要求的。這兩個問題雖然題面看起來大相徑庭,但是核心的本質是一樣的。

我們來回憶一下,走迷宮問題應該怎麼解決?

這個答案應該已經非常確定了,當然是搜索算法。我們需要搜索解可能存在的空間去尋找存在的解,也就是說我們面臨的是一個解是否存在的問題,要麼找到解,要麼遍歷完所有的可能性發現解不存在。確定了是搜索算法之後,剩下的就簡單了,我們只有兩個選項,深度優先或者是廣度優先。

理論上來說,一般判斷解的存在性問題,我們使用廣度優先搜索更多,因為一般來說它可以更快地找到解。但是本題當中有一個小問題是,廣度優先搜索需要在隊列當中存儲中間狀態,需要記錄地圖上行走過的信息,每有一個狀態就需要存儲一份地圖信息,這會帶來比較大的內存開銷,同樣存儲的過程也會帶來計算開銷,在這道題當中,這是不可以接受的。拷貝狀態帶來的空間消耗還是小事,關鍵是拷貝帶來的時間開銷,就足夠讓這題超時了。所以我們別無選擇,只能深度優先。

明確了算法之後,只剩下了最後一個問題,在這個走迷宮問題當中,我們怎麼找到迷宮的入口呢?因為題目當中並沒有規定我們起始點的位置,這也不難解決,我們遍歷二維的字符數組,和字符串開頭相匹配的位置都可以作為迷宮的入口。

最後,我們來看代碼,並沒有什麼技術含量,只是簡單的回溯法而已。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        fx = [[0, 1], [0, -1], [1, 0], [-1, 0]]
        def dfs(x, y, l):
            if l == len(word):
                return True
            for i in range(4):
                nx = x + fx[i][0]
                ny = y + fx[i][1]
                # 出界或者是走過的時候,跳過
                if nx < 0 or nx == n or ny < 0 or ny == m or visited[nx][ny]:
                    continue
                if board[nx][ny] == word[l]:
                    visited[nx][ny] = 1
                    if dfs(nx, ny, l+1):
                        return True
                    visited[nx][ny] = 0
            return False
                
        n = len(board)
        if n == 0:
            return False
        m = len(board[0])
        if m == 0:
            return False
        
        visited = [[0 for i in range(m)] for j in range(n)]
        
        for i in range(n):
            for j in range(m):
                # 找到合法的起點
                if board[i][j] == word[0]:
                    visited = [[0 for _ in range(m)] for _ in range(n)]
                    visited[i][j] = 1
                    if dfs(i, j, 1):
                        return True
                    
        return False

總結

如果能夠想通回溯法,並且對於回溯法的實現足夠熟悉,那麼這題的難度是不大的。實際上至今為止,我們一路刷來,已經做了好幾道回溯法的問題了,我想對你們來說,回溯法的問題應該已經小菜一碟了。

相比於回溯法來說,我覺得更重要的是我們能夠通過分析想清楚,為什麼廣度優先搜索不行,底層核心的本質原因是什麼。這個思考的過程往往比最後的結論來得重要。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

Spring Boot 2.x基礎教程:Spring Data JPA的多數據源配置

上一篇我們介紹了在使用JdbcTemplate來做數據訪問時候的多數據源配置實現。接下來我們繼續學習如何在使用Spring Data JPA的時候,完成多數據源的配置和使用。

添加多數據源的配置

先在Spring Boot的配置文件application.properties中設置兩個你要鏈接的數據庫配置,比如這樣:

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=123456
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

# 日誌打印執行的SQL
spring.jpa.show-sql=true
# Hibernate的DDL策略
spring.jpa.hibernate.ddl-auto=create-drop

這裏除了JPA自身相關的配置之外,與JdbcTemplate配置時候的數據源配置完全是一致的

說明與注意

  1. 多數據源配置的時候,與單數據源不同點在於spring.datasource之後多設置一個數據源名稱primarysecondary來區分不同的數據源配置,這個前綴將在後續初始化數據源的時候用到。
  2. 數據源連接配置2.x和1.x的配置項是有區別的:2.x使用spring.datasource.secondary.jdbc-url,而1.x版本使用spring.datasource.secondary.url。如果你在配置的時候發生了這個報錯java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.,那麼就是這個配置項的問題。

初始化數據源與JPA配置

完成多數據源的配置信息之後,就來創建個配置類來加載這些配置信息,初始化數據源,以及初始化每個數據源要用的JdbcTemplate。

由於JPA的配置要比JdbcTemplate的負責很多,所以我們將配置拆分一下來處理:

  1. 單獨建一個多數據源的配置類,比如下面這樣:
@Configuration
public class DataSourceConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

可以看到內容跟JdbcTemplate時候是一模一樣的。通過@ConfigurationProperties可以知道這兩個數據源分別加載了spring.datasource.primary.*spring.datasource.secondary.*的配置。@Primary註解指定了主數據源,就是當我們不特別指定哪個數據源的時候,就會使用這個Bean真正差異部分在下面的JPA配置上。

  1. 分別創建兩個數據源的JPA配置。

Primary數據源的JPA配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= { "com.didispace.chapter38.p" }) //設置Repository所在位置
public class PrimaryConfig {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource primaryDataSource;

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
    }

    @Primary
    @Bean(name = "entityManagerPrimary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(primaryDataSource)
                .packages("com.didispace.chapter38.p") //設置實體類所在位置
                .persistenceUnit("primaryPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }

}

Secondary數據源的JPA配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "com.didispace.chapter38.s" }) //設置Repository所在位置
public class SecondaryConfig {

    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

    private Map<String, Object> getVendorProperties() {
        return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
    }

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(secondaryDataSource)
                .packages("com.didispace.chapter38.s") //設置實體類所在位置
                .persistenceUnit("secondaryPersistenceUnit")
                .properties(getVendorProperties())
                .build();
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
    }

}

說明與注意

  • 在使用JPA的時候,需要為不同的數據源創建不同的package來存放對應的Entity和Repository,以便於配置類的分區掃描
  • 類名上的註解@EnableJpaRepositories中指定Repository的所在位置
  • LocalContainerEntityManagerFactoryBean創建的時候,指定Entity所在的位置
  • 其他主要注意在互相注入時候,不同數據源不同配置的命名,基本就沒有什麼大問題了

測試一下

完成了上面之後,我們就可以寫個測試類來嘗試一下上面的多數據源配置是否正確了,比如下面這樣:

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter38ApplicationTests {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MessageRepository messageRepository;

    @Test
    public void test() throws Exception {
        userRepository.save(new User("aaa", 10));
        userRepository.save(new User("bbb", 20));
        userRepository.save(new User("ccc", 30));
        userRepository.save(new User("ddd", 40));
        userRepository.save(new User("eee", 50));

        Assert.assertEquals(5, userRepository.findAll().size());

        messageRepository.save(new Message("o1", "aaaaaaaaaa"));
        messageRepository.save(new Message("o2", "bbbbbbbbbb"));
        messageRepository.save(new Message("o3", "cccccccccc"));

        Assert.assertEquals(3, messageRepository.findAll().size());
    }

}

說明與注意

  • 測試驗證的邏輯很簡單,就是通過不同的Repository往不同的數據源插入數據,然後查詢一下總數是否是對的
  • 這裏省略了Entity和Repository的細節,讀者可以在下方代碼示例中下載完整例子對照查看

代碼示例

本文的相關例子可以查看下面倉庫中的chapter3-8目錄:

  • Github:https://github.com/dyc87112/SpringBoot-Learning/
  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

如果您覺得本文不錯,歡迎Star支持,您的關注是我堅持的動力!

相關閱讀

  • Spring Boot 1.x基礎教程:多數據源配置

本文首發:Spring Boot 2.x基礎教程:Spring Data JPA的多數據源配置,轉載請註明出處。
歡迎關注我的公眾號:程序猿DD,獲得獨家整理的學習資源和日常乾貨推送。
如果您對我的其他專題內容感興趣,直達我的個人博客:didispace.com。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

手把手教你基於SqlSugar4編寫一個可視化代碼生成器(生成實體,以SqlServer為例,文末附源碼)

  在開發過程中免不了創建實體類,字段少的表可以手動編寫,但是字段多還用手動創建的話不免有些浪費時間,假如一張表有100多個字段,手寫有些不現實。

這時我們會藉助一些工具,如:動軟代碼生成器、各種ORM框架自帶的代碼生成器等等,都可以使用。

我們現在就基於SqlSugar(ORM框架)自己動手製造一個輪子,以SqlServer為例。我們先看一下成品效果,

 

使用流程:

  配置好數據庫鏈接,點擊【鏈接數據庫】獲取指定服務器上的數據庫名,點擊數據庫名,動態獲取數據庫下面的所有表,

點擊數據表,如果生成過了的會自動獲取生成的實體,如果沒有生成過,點擊【生成實體】自動生成显示,直接複製即可使用。

注:server=xxx.xxx.x.xxx這裏如果是本地沒有配置的話直接server=.即可。

 

 

 

 

開發環境:

編譯器:Visual Studio 2017

運行環境:windows7 x64

數據庫:SqlServer2012

 

代碼實現步驟:

一、創建一個ASP.NET Web應用,命名為GenerateEntity

 

 

 

 

 

 

二、應用SqlSugar動態鏈接庫

 

 

 

三、編寫代碼

這裏分為前端和後端,前端頁面展示,後端後台邏輯(注:由於我們是代碼展示,所以就不搞三層架構、工廠模式這些,直接在控制器中完成,有需要的同學可以根據項目需求進行更改

內部實現邏輯:

  • 在頁面上配置數據庫鏈接,點擊【鏈接數據庫】按鈕獲取指定數據庫的所有數據庫名显示在左邊;
  • 點擊左邊的數據庫名稱,動態獲取指定數據庫下面所有的表显示出來;
  • 點擊表名,生成過的就显示生成的實體,沒有的則點擊【生成實體】按鈕生成(支持生成單表和數據庫表全部生成);

這裏我直接貼出代碼,直接拷貝即可使用:

前端html頁面

@{
    ViewBag.Title = "Home Page";
}

<script src="~/Scripts/jquery-3.3.1.js"></script>

<div style="margin-top:10px;font-family:'Microsoft YaHei';font-size:18px; ">
    <div style="height:100px;width:100%;border:1px solid gray;padding:10px">
        <div>
            <span>鏈接數據庫:</span>
            <input style="width:800px;max-width:800px;" id="Link" value="server=xxx.xxx.x.xxx;uid=sa;pwd=xxx" />
            <a href="javascript:void(0)" onclick="LinkServer()">鏈接數據庫</a>
        </div>
        <div style="margin-top:10px">
            <span>數據庫名:</span>
            <input style="color:red;font-weight:600" id="ServerName" />

            <span>表名:</span>
            <input style="color:red;font-weight:600" id="TableName" />

            <span>生成類型:</span>
            <select id="type">
                <option value="0">生成單個表</option>
                <option value="1">生成所有表</option>
            </select>
            <a  href="javascript:void(0)" onclick="GenerateEntity()" style="margin-left:20px;font-weight:600;">生成實體</a>
            <br />

        </div>
    </div>
    <div style="height:720px;width:100%;">
        <div style="height:100%;width:40%;float:left; border:1px solid gray;font-size:20px">

            <div id="leftserver" style="float:left;border:1px solid gray;height:100%;width:40%;padding:10px;overflow: auto;">

            </div>
            <div id="lefttable" style="float:left;border:1px solid gray;height:100%;width:60%;padding:10px;overflow: auto;">

            </div>
        </div>
        <div  style="height:100%;width:60%;float:left;border:1px solid gray;overflow: auto;">
            <textarea style="width:100%;height:100%;max-width:10000px" id="righttable"></textarea>
        </div>
    </div>
</div>

<script type="text/javascript">

    //鏈接數據庫
    function LinkServer() {
        $.ajax({
            url: "/Home/LinkServer",
            data: { Link: $("#Link").val() },
            type: "POST",
            async: false,
            dataType: "json",
            success: function (data) {
                if (data.res) {
                    if (data.info != "") {
                        $("#leftserver").html("");
                        var leftserver = "<span>數據庫名</span><hr />";
                        var info = eval("(" + data.info + ")");
                        for (var i = 0; i < info.length; i++) {
                            leftserver += "<a onclick=\"leftserver('" + info[i].Name + "')\">" + info[i].Name + "</a><br />";
                        }

                        $("#leftserver").html(leftserver);
                    }
                }
                else {
                    alert(data.msg);
                }
            }
        });
    }

    //查詢指定數據庫的表
    function leftserver(Name) {
        $("#ServerName").val(Name)
        $.ajax({
            url: "/Home/GetTable",
            data: { Link: $("#Link").val(), Name: Name },
            type: "POST",
            async: false,
            dataType: "json",
            success: function (data) {
                if (data.res) {
                    if (data.info != "") {
                        $("#lefttable").html("");
                        var lefttable = "<span>表名</span><hr />";
                        var info = eval("(" + data.info + ")");
                        for (var i = 0; i < info.length; i++) {
                            lefttable += "<a onclick=\"lefttable('" + info[i].Name + "')\">" + info[i].Name + "</a><br />";
                        }

                        $("#lefttable").html(lefttable);
                    }
                }
                else {
                    alert(data.msg);
                }
            }
        });
    }

    //查詢指定數據庫的表
    function lefttable(Name) {
        $("#TableName").val(Name);
        $.ajax({
            url: "/Home/GetGenerateEntity",
            data: { TableName: Name },
            type: "POST",
            async: false,
            dataType: "json",
            success: function (data) {
                if (data.res) {
                    document.getElementById("righttable").innerHTML = data.info;
                }
                else {
                    alert(data.msg);
                }
            }
        });
    }

    //生成實體
    function GenerateEntity() {

        $.ajax({
            url: "/Home/GenerateEntity",
            data: {
                Link: $("#Link").val(),
                Name: $("#ServerName").val(),
                TableName: $("#TableName").val(),
                type: $("#type").val()
            },
            type: "POST",
            async: false,
            dataType: "json",
            success: function (data) {
                if (data.res) {
                    document.getElementById("righttable").innerHTML = data.info;
                }
                else {
                    alert(data.msg);
                }
            }
        });
    }

</script>

 

後端控制器數據

using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace GenerateEntity.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }


     
        //鏈接數據庫
        public JsonResult LinkServer(string Link)
        {
            ResultInfo result = new ResultInfo();
            try
            {
                //配置數據庫連接
                SqlSugarClient db = new SqlSugarClient(
                                    new ConnectionConfig()
                                    {
                                        ConnectionString = ""+ Link + ";database=master",
                                        DbType = DbType.SqlServer,//設置數據庫類型
                                    IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                    InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                });
                string sql = @"SELECT top 100000 Name FROM Master..SysDatabases ORDER BY Name";  //查詢所有鏈接的所有數據庫名
                var strList = db.SqlQueryable<databaseName>(sql).ToList();
                result.info = Newtonsoft.Json.JsonConvert.SerializeObject(strList);
                result.res = true;
                result.msg = "鏈接成功!";
            }
            catch (Exception ex)
            {
                result.msg = ex.Message;
            }

            return Json(result, JsonRequestBehavior.AllowGet);
        }

        //根據數據庫名查詢所有表
        public JsonResult GetTable(string Link,string Name)
        {

            ResultInfo result = new ResultInfo();
            try
            {
                //配置數據庫連接
                SqlSugarClient db = new SqlSugarClient(
                                    new ConnectionConfig()
                                    {
                                        ConnectionString = "" + Link + ";database="+ Name + "",
                                        DbType = DbType.SqlServer,//設置數據庫類型
                                        IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                        InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                    });

                string sql = @"SELECT top 10000 Name FROM SYSOBJECTS WHERE TYPE='U' ORDER BY Name";  //查詢所有鏈接的所有數據庫名
                var strList = db.SqlQueryable<databaseName>(sql).ToList();
                result.info = Newtonsoft.Json.JsonConvert.SerializeObject(strList);
                result.res = true;
                result.msg = "查詢成功!";
            }
            catch (Exception ex)
            {
                result.msg = ex.Message;
            }

            return Json(result, JsonRequestBehavior.AllowGet);
        }

        //生成實體
        public JsonResult GenerateEntity(string Link, string Name,string TableName,string type)
        {

            ResultInfo result = new ResultInfo();
            try
            {
                //配置數據庫連接
                SqlSugarClient db = new SqlSugarClient(
                                    new ConnectionConfig()
                                    {
                                        ConnectionString = "" + Link + ";database=" + Name + "",
                                        DbType = DbType.SqlServer,//設置數據庫類型
                                        IsAutoCloseConnection = true,//自動釋放數據務,如果存在事務,在事務結束后釋放
                                        InitKeyType = InitKeyType.Attribute //從實體特性中讀取主鍵自增列信息
                                    });

                string path = "C:\\Demo\\2";

                if (type == "0")
                {
                    path = "C:\\Demo\\2";
                    db.DbFirst.Where(TableName).CreateClassFile(path);
                    result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                }
                else if (type == "1")
                {
                    path = "C:\\Demo\\3";
                    db.DbFirst.IsCreateAttribute().CreateClassFile(path);
                    result.info = "";
                }

                
                
                result.res = true;
                result.msg = "生成成功!";
            }
            catch (Exception ex)
            {
                result.msg = ex.Message;
            }

            return Json(result, JsonRequestBehavior.AllowGet);
        }

        //生成全部表時查看
        public JsonResult GetGenerateEntity(string TableName)
        {

            ResultInfo result = new ResultInfo();
            try
            {
                string path = "C:\\Demo\\3";
                result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                result.res = true;
                result.msg = "查詢成功!";
            }
            catch (Exception ex)
            {
                result.msg = ex.Message;
                try
                {
                    if (result.msg.Contains("未能找到文件"))
                    {
                       string path = "C:\\Demo\\2";
                        result.info = System.IO.File.ReadAllText(@"" + path + "\\" + TableName + ".cs" + "", Encoding.UTF8);
                        result.res = true;
                        result.msg = "查詢成功!";
                    }
                }
                catch (Exception)
                {
                    result.msg = ex.Message;
                }
            }

            return Json(result, JsonRequestBehavior.AllowGet);
        }

        //數據庫名
        public class databaseName
        {
            public string Name { get; set; }
        }

        //封裝返回信息數據
        public class ResultInfo
        {
            public ResultInfo()
            {
                res = false;
                startcode = 449;
                info = "";
            }
            public bool res { get; set; }  //返回狀態(true or false)
            public string msg { get; set; }  //返回信息
            public int startcode { get; set; }  //返回http的狀態碼
            public string info { get; set; }  //返回的結果(res為true時返回結果集,res為false時返回錯誤提示)
        }

    }
}

 

 

 

這樣一套可視化代碼生成器就出來了,我們把他發布到IIS上面,然後設置為瀏覽器標籤(收藏),這樣就可以快捷使用了。

我們運行一下看看,是不是感覺很方便呀!

 

 

 

歡迎關注訂閱我的微信公眾平台【熊澤有話說】,更多好玩易學知識等你來取
作者:熊澤-學習中的苦與樂
公眾號:熊澤有話說
出處: https://www.cnblogs.com/xiongze520/p/13181241.html
創作不易,版權歸作者和博客園共有,轉載或者部分轉載、摘錄,請在文章明顯位置註明作者和原文鏈接。  

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

程序員如何高效學Python,如何高效用Python掙錢

    本人在1年半之前,不熟悉Python(不過有若干年Java開發基礎),由於公司要用Python,所以學習了一通。現在除了能用Python做本職工作外,還出了本Python書,《基於股票大數據分析的Python入門實戰 視頻教學版》,京東鏈接:https://item.jd.com/69241653952.html,還在某網站錄製了視頻課,後面還有其它線上線下課的機會。

    

    本人的感受是,哪怕上班用不到Python,程序員也應該學Python,因為Python能給大家帶來更多的主業副業機會,而且現在做Python的人還沒Java多。在本文里將結合本人的經驗,一方面分享下如何高效學Python,另一方面分享下用Python掙錢的經驗。

1 先說Python的尷尬地位,首先要明確掙錢方式

    尷尬體現在哪裡?一些大廠雖然有專門做Python的高薪崗位,但一般會直接找些深度學習機器學習方向的碩士博士,而且是要名校的,而小公司一般限於成本的原因,無法直接設置單做Python的崗位,而且也就是做些技術含量較低的應用, 比如做個爬蟲或者簡單調個機器學習的庫,所以一般是讓做其它語言的人順帶做掉。

    而且Python用庫和方法的形式包裝掉了一些很複雜的算法,程序員一般只需要調用方法就可以實現基本的機器學習和深度學習之類的活,而在大廠里高薪的Python崗,絕非是簡單地調用Python,而是需要深入了解算法,從而根據業務定製模型,所以一般社會上的程序員很難通過自學,達到大廠里高薪Python程序員的標準。

    總之,你在工作后通過自學Python,未必能達到大廠高薪職位的標準,因為由於你數學基礎不行,未必能深入算法,而一般公司也不會單獨開設Python崗位,所以對應地,學Python之前大家應該明確靠Python的掙錢方式。

    1 主業上,還得以Java等語言為主,但如果你能在簡歷和面試中證明自己很精通一般的Python爬蟲、數據分析和機器學習等方面的應用,絕對能幫你更好地找到工作,並且個人提升也會很快。

    2 雖然Python底層包含的深度學習等方面的算法很難,但用Python做案例並不難,大家可以通過Python做些副業的活。

2 再說Python該怎麼學,該學哪些技能?

    第一步,了解Python的基本語法,比如集合,讀寫文件,讀寫數據庫和異常處理等,如果大家有Java等語言的開發基礎,這塊很簡單,本人也就用了2個星期。但正是因為簡單,所以這些技能很不值錢,別人學起來也快。

    第二步,了解數據分析三劍客,具體來說就是Numpy, Pandas和matplotlib,用Numpy和Pandas清洗數據,用Pandas的DataFrame存儲數據,再用matplotlib繪製柱狀圖餅圖之類的圖形。

    第三步,了解爬蟲技能,這裏除了需要了解自帶的urllib庫之外,還需要了解一種框架,比如Scrapy,需要到能根據需求定製爬蟲代碼的程序。

    其實數據分析和爬蟲相關的語法技能,也不複雜,本人用1個半月也就達到能幹活的程度了,相信大家應該更快。而且,學到這種程序,應該就可以去做些案例以此掙錢了,比如寫分析xx網站的案例,錄成視頻去賣了,而且也能完成公司里大多數數據獲取和數據分析的功能需求了。

    第四步去了解機器學習庫,具體而言就去學習sklearn庫,這個庫里不僅包含了線性回歸嶺回歸和SVM分類等機器學習算法,還包含了波士頓房價、鳶尾花和手寫體識別等的數據集,而且由於已經包裝了相關算法,用sklearn庫學習機器學習的過程並不難,不需要過多的數學知識。學好這個庫,外帶結合爬蟲和數據分析的技能,就更在某個領域幹活掙錢了,比如本人在股票分析領域出了本書,並且也出了些視頻,後繼還可以繼續深入股票量化分析領域。

    第五步,可以去了解深度學習,無非是人工神經網絡,自然語言分析,圖像識別等,這方面雖然包含的數學知識更複雜,但由於也經過包裝,所以直接用接口也不難。這方面學好以後,雖然說高不成低不就,即沒法進大廠,同時小公司也用不到,但用這些知識準備些案例,出書講課錄視頻,甚至做企業培訓,還是能帶來一定的收益的。

    在學上述知識的時候,千萬不能只學語法,因為沒用,一定得結合實例,同時把這些知識變現的時候,也不能單講語法,也是要準備若干案例,比如像我這樣的股票分析,或者是scrapy+數據分析+深度學習的xx網站數據分析案例,這些技能雖然很高大上,但其實做到調用接口實踐案例的程度就能掙錢,如果再有機緣以此進入大廠,那就真的前途無量了。

3 可以先從公眾號做起

    之前講的是如何學,學什麼,這裏就開始講如何掙錢。當然最簡單的就是建個公眾號,在上面發文,吸引粉絲,這個門檻相對低。

    但注意如果僅僅發表入門級的文章,比如numpy庫怎麼用,怎麼用matplotlib庫繪製基本圖形,這絕對不夠,因為此類文章太多,哪些文章能吸引人?

    1 綜合應用類,比如scrapy+數據分析三劍客。

    2 實戰案例類,比如用scrapy爬個網站數據,然後分析。

    3 專業領域類,比如量化分析股票,分析房價等。

    4 深度學習機器學習這些領域現在還很火,這些領域如果把某個算法通俗易懂地講透也行,或者這些方面給寫案例,比如用自然語言分析技術分析某網站的評論等。

    如果能定期發表此類文章,公眾號一定能聚集到不少粉絲,同樣也可以做視頻的up主。

4 更可以出本屬於你的書

    如何讓別人認為你是python某個領域的大牛?要麼有大廠架構師職位加持,這不是每個人都能達到,或者是著名博主公眾號主,但似乎這也需要經歷來積澱,不過如果你在python數據分析和機器學習等方面出本書,那說服力自然就上來了。

    出書可以偏重案例,比如講爬蟲數據分析的書,在合法的前提下給出爬取分析若干知名網頁的案例,如果講機器學習的書,甚至可以結合sklearn庫自帶的數據集,講清楚常用算法的案例應用,如果有時間有機會,我甚至打算再出版本基於python股票量化的書。

    相對而言,寫一本講述包括語法、結合小案例講(機器學習等)庫的用法和結合綜合案例講機器學習算法和數據分析綜合應用的書,並不難。對於一個有5年開發經驗的程序員而言,從零基礎積累個半年,就完全可以達到出書的地步,如果資歷稍微弱些,只有2,3年開發經歷,估計學個1年也應該可以達到出書的地步。

    還是這句話,出書掙的錢不多,但絕對能證明你在python某個領域的能力,小到聯繫副業,大到以此找工作,一定能幫到你。具體操作的話,可以直接在清華出版社,机械工業出版社,人民郵電出版社和电子工業出版社的官網找聯繫方式,然後直接和編輯溝通,至於一些有中介性質的圖書公司,大家自己看着辦。

5 也可以做其它副業

    包括到各大視頻網站去錄製數據分析、爬蟲、機器學習和深度學習等方面的系列課,也可以找你所在城市的線下培訓班去講課,如果你有相關大公司背景,有自己的書,或者業內知名,你就可以聯繫些做企業培訓的公司。這樣做下個半年後,月入1萬應該不是問題。

    在做各種副業的時候,一般來說也是要偏重案例,比如你有若干個深度學習的案例外帶相關算法的說明,再加上些好的文案,應該很能吸引人。當然也可以直接找項目做,目前python方面比較熱門的項目可能還是用爬蟲,但這塊做的時候就要非常慎重了,不能做違法的事情,而當前用到深度學習機器學習技術的項目倒不多,可能因為這些應用更集中在大公司吧。

6 總結:下班后不能總放鬆,更得找點事干

    說實話,python方面的活,哪怕門檻最低的做公眾號,要做好也不簡單,更何況出書了。至於,自己聯繫平台出視頻,或者做線下培訓或者做項目,就不僅得靠技術,更得靠人脈了,經營這類活需要的時間更多。

    不過掙錢拿有容易的,況且,如果下班后總是看手機或者混日子,可能一天天就很快過去了,與其下班做些沒收益的消遣,還不如學些python幹些活,這樣多少好歹也有收益,或者指不定無心插柳柳成蔭,你經營python一段時間后,或者真就以此進了大廠,或者也通過各種途徑成為業內知名人事,拓展了不少副業渠道,也算是不負好時光吧。

    感謝大家看完此文,如果感覺有一定道理,請點贊此文。如果要轉載,也請全文轉載,別刪節本人辛苦寫成的文章。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!