Angular 從入坑到挖坑 – 路由守衛連連看_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

一、Overview

Angular 入坑記錄的筆記第六篇,介紹 Angular 路由模塊中關於路由守衛的相關知識點,了解常用到的路由守衛接口,知道如何通過實現路由守衛接口來實現特定的功能需求,以及實現對於特性模塊的惰性加載

對應官方文檔地址:

  • 路由與導航

配套代碼地址:angular-practice/src/router-combat

二、Contents

  1. Angular 從入坑到棄坑 – Angular 使用入門
  2. Angular 從入坑到挖坑 – 組件食用指南
  3. Angular 從入坑到挖坑 – 表單控件概覽
  4. Angular 從入坑到挖坑 – HTTP 請求概覽
  5. Angular 從入坑到挖坑 – Router 路由使用入門指北
  6. Angular 從入坑到挖坑 – 路由守衛連連看

三、Knowledge Graph

四、Step by Step

4.1、基礎準備

重複上一篇筆記的內容,搭建一個包含路由配置的 Angualr 項目

新建四個組件,分別對應於三個實際使用到的頁面與一個設置為通配路由的 404 頁面

-- 危機中心頁面
ng g component crisis-list

-- 英雄中心頁面
ng g component hero-list

-- 英雄相親頁面
ng g component hero-detail

-- 404 頁面
ng g component page-not-found 

在 app-routing.module.ts 文件中完成對於項目路由的定義,這裏包含了對於路由的重定向、通配路由,以及通過動態路由進行參數傳遞的使用

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
  },
  {
    path: 'heroes',
    component: HeroListComponent,
  },
  {
    path: 'hero/:id',
    component: HeroDetailComponent,
  },
  {
    path: '',
    redirectTo: '/heroes',
    pathMatch: 'full',
  },
  {
    path: '**',
    component: PageNotFoundComponent,
  }
];

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

之後,在根組件中,添加 router-outlet 標籤用來聲明路由在頁面上渲染的出口

<h1>Angular Router</h1>
<nav>
  <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> &nbsp;&nbsp;
  <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

4.2、路由守衛

在 Angular 中,路由守衛主要可以解決以下的問題

  • 對於用戶訪問頁面的權限校驗(是否已經登錄?已經登錄的角色是否有權限進入?)
  • 在跳轉到組件前獲取某些必須的數據
  • 離開頁面時,提示用戶是否保存未提交的修改

Angular 路由模塊提供了如下的幾個接口用來幫助我們解決上面的問題

  • CanActivate:用來處理系統跳轉到到某個路由地址的操作(判斷是否可以進行訪問)
  • CanActivateChild:功能同 CanActivate,只不過針對的是子路由
  • CanDeactivate:用來處理從當前路由離開的情況(判斷是否存在未提交的信息)
  • CanLoad:是否允許通過延遲加載的方式加載某個模塊

在添加了路由守衛之後,通過路由守衛返回的值,從而達到我們控制路由的目的

  • true:導航將會繼續
  • false:導航將會中斷,用戶停留在當前的頁面或者是跳轉到指定的頁面
  • UrlTree:取消當前的導航,並導航到路由守衛返回的這個 UrlTree 上(一個新的路由信息)
4.2.1、CanActivate:認證授權

在實現路由守衛之前,可以通過 Angular CLI 來生成路由守衛的接口實現類,通過命令行,在 app/auth 路徑下生成一個授權守衛類,CLI 會提示我們選擇繼承的路由守衛接口,這裏選擇 CanActivate 即可

ng g guard auth/auth

在 AuthGuard 這個路由守衛類中,我們模擬了是否允許訪問一個路由地址的認證授權。首先判斷是否已經登錄,如果登錄后再判斷當前登錄人是否具有當前路由地址的訪問權限

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token === 'admin' && url === '/crisis-center') {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }
}

之後我們就可以在 app-routing.module.ts 文件中引入 AuthGuard 類,針對需要保護的路由進行路由守衛的配置

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';

// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
    canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
  }
];

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

4.2.2、CanActivateChild:針對子路由的認證授權

與繼承 CanActivate 接口進行路由守衛的方式相似,針對子路由的認證授權可以通過繼承 CanActivateChild 接口來實現,因為授權的邏輯很相似,這裏通過多重繼承的方式,擴展 AuthGuard 的功能,從而達到同時針對路由和子路由的路由守衛

改造下原先 canActivate 方法的實現,將認證邏輯修改為用戶的 token 信息中包含 admin 即可訪問 crisis-center 頁面,在針對子路由進行認證授權的 canActivateChild 方法中,通過判斷 token 信息是否為 admin-master 模擬完成對於子路由的訪問認證

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    return token === 'admin-master';
  }
}

通過 Angular CLI 新增一個 crisis-detail 組件,作為 crisis-list 的子組件

ng g component crisis-detail

接下來在 crisis-list 中添加 router-outlet 標籤,用來定義子路由的渲染出口

<h2>危機中心</h2>

<ul class="crises">
  <li *ngFor="let crisis of crisisList">
    <a [routerLink]="[crisis.id]">
      <span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
    </a>
  </li>
</ul>

<!-- 定義子路由的渲染出口 -->
<router-outlet></router-outlet>

在針對子路由的認證授權配置時,我們可以選擇針對每個子路由添加 canActivateChild 屬性,也可以定義一個空地址的子路由,將所有歸屬於 crisis-list 的子路由作為這個空路由的子路由,通過針對這個空路徑添加 canActivateChild 屬性,從而實現將守護規則應用到所有的子路由上

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

這裏其實相當於將原先兩級的路由模式(父:crisis-list,子:crisis-detail)改成了三級(父:crisis-list,子:’ ‘(空路徑),孫:crisis-detail)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';

// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';

const routes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisListComponent,
    canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
    children: [{
      path: '',
      canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
      children: [{
        path: 'detail',
        component: CrisisDetailComponent
      }]
    }]
  }
];

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

4.2.3、CanDeactivate:處理用戶未提交的修改

當進行表單填報之類的操作時,因為會涉及到一個提交的動作,當用戶沒有點擊保存按鈕就離開時,最好能暫停,對用戶進行一個友好性的提示,由用戶選擇後續的操作

創建一個路由守衛,繼承於 CanDeactivate 接口

ng g guard hero-list/guards/hero-can-deactivate

與上面的 CanActivate、CanActivateChild 路由守衛的使用方式不同,對於 CanDeactivate 守衛來說,我們需要將參數中的 unknown 替換成我們實際需要進行路由守衛的組件

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class HeroCanDeactivateGuard implements CanDeactivate<unknown> {
  canDeactivate(
    component: unknown,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
  
}

例如,這裏針對的是 HeroListComponent 這個組件,因此我們需要將泛型的參數 unknown 改為 HeroListComponent,通過 component 參數,就可以獲得需要進行路由守衛的組件的相關信息

import { Injectable } from '@angular/core';
import {
  CanDeactivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';

// 引入需要進行路由守衛的組件
import { HeroListComponent } from '../hero-list.component';

@Injectable({
  providedIn: 'root',
})
export class HeroCanDeactivateGuard
  implements CanDeactivate<HeroListComponent> {
  canDeactivate(
    component: HeroListComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {

    // 判斷是否修改了原始數據
    //
    const data = component.hero;
    if (data === undefined) {
      return true;
    }
    const origin = component.heroList.find(hero => hero.id === data.id);
    if (data.name === origin.name) {
      return true;
    }

    return window.confirm('內容未提交,確認離開?');
  }
}

這裏模擬判斷用戶有沒有修改原始的數據,當用戶修改了數據並移動到別的頁面時,觸發路由守衛,提示用戶是否保存后再離開當前頁面

4.3、異步路由

4.3.1、惰性加載

當應用逐漸擴大,使用現有的加載方式會造成應用在第一次訪問時就加載了全部的組件,從而導致系統首次渲染過慢。因此這裏可以使用惰性加載的方式在請求具體的模塊時才加載對應的組件

惰性加載只針對於特性模塊(NgModule),因此為了使用惰性加載這個功能點,我們需要將系統按照功能劃分,拆分出一個個獨立的模塊

首先通過 Angular CLI 創建一個危機中心模塊(crisis 模塊)

-- 查看創建模塊的相關參數
ng g module --help

-- 創建危機中心模塊(自動在 app.moudule.ts 中引入新創建的 CrisisModule、添加當前模塊的路由配置)
ng g module crisis --module app --routing

將 crisis-list、crisis-detail 組件全部移動到 crisis 模塊下面,並在 CrisisModule 中添加對於 crisis-list、crisis-detail 組件的聲明,同時將原來在 app.module.ts 中聲明的組件代碼移除

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { CrisisRoutingModule } from './crisis-routing.module';

import { FormsModule } from '@angular/forms';

// 引入模塊中使用到的組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';


@NgModule({
  declarations: [
    CrisisListComponent,
    CrisisDetailComponent
  ],
  imports: [
    CommonModule,
    FormsModule,
    CrisisRoutingModule
  ]
})
export class CrisisModule { }

同樣的,將當前模塊的路由配置移動到專門的路由配置文件 crisis-routing.module.ts 中,並將 app-routing.module.ts 中相關的路由配置刪除

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';

// 引入路由守衛
import { AuthGuard } from '../auth/auth.guard';

const routes: Routes = [{
  path: '',
  component: CrisisListComponent,
  canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
  children: [{
    path: '',
    canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
    children: [{
      path: 'detail',
      component: CrisisDetailComponent
    }]
  }]
}];

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

重新運行項目,如果你在創建模塊的命令中設置了自動引入當前模塊到 app.module.ts 文件中,大概率會遇到下面的問題

這裏的問題與配置通配路由需要放到最後的原因相似,因為腳手架在幫我們將創建的模塊導入到 app.module.ts 中時,是添加到整個數組的最後,同時因為我們已經將 crisis 模塊的路由配置移動到專門的 crisis-routing.module.ts 中了,框架在進行路由匹配時會預先匹配上 app-routing.module.ts 中設置的通配路由,從而導致無法找到實際應該對應的組件,因此這裏我們需要將 AppRoutingModule 放到聲明的最後

當問題解決后,就可以針對 crisis 模塊設置惰性加載

在配置惰性路由時,我們需要以一種類似於子路由的方式進行配置,通過路由的 loadChildren 屬性來加載對應的模塊,而不是具體的組件,修改后的 AppRoutingModule 代碼如下

import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'crisis-center',
    loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
  }
];

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

當導航到這個 /crisis-center 路由時,框架會通過 loadChildren 字符串來動態加載 CrisisModule,然後把 CrisisModule 添加到當前的路由配置中,而惰性加載和重新配置工作只會發生一次,也就是在該路由首次被請求時執行,在後續請求時,該模塊和路由都是立即可用的

4.3.2、CanLoad:杜絕未通過認證授權的組件加載

在上面的代碼中,對於 CrisisModule 模塊我們已經使用 CanActivate、CanActivateChild 路由守衛來進行路由的認證授權,但是當我們並沒有權限訪問該路由的權限,卻依然點擊了鏈接時,此時框架路由仍會加載該模塊。為了杜絕這種授權未通過仍加載模塊的問題發生,這裏需要使用到 CanLoad 守衛

因為這裏的判斷邏輯與認證授權的邏輯相同,因此在 AuthGuard 中,繼承 CanLoad 接口即可,修改后的 AuthGuard 代碼如下

import { Injectable } from '@angular/core';
import {
  CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild, CanLoad, Route, UrlSegment
} from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {

  /**
   * ctor
   * @param router 路由
   */
  constructor(private router: Router) { }


  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    // 判斷是否有 token 信息
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    // 判斷是否可以訪問當前連接
    let url: string = state.url;
    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }

    this.router.navigate(['/login']);
    return false;
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    return token === 'admin-master';
  }

  canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
    let token = localStorage.getItem('auth-token') || '';
    if (token === '') {
      this.router.navigate(['/login']);
      return false;
    }

    let url = `/${route.path}`;

    if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
      return true;
    }
  }
}

同樣的,針對路由守衛的實現完成后,將需要使用到的路由守衛添加到 crisis-center 路由的 canLoad 數組中即可實現授權認證不通過時不加載模塊

import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'crisis-center',
    loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
  }
];

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

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

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

從聚合支付業務的設計來聊聊策略模式_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

六月福利

2020年6月公眾號碼農小胖哥原創文章轉發第一名將送全新《Spring Boot實戰》實體書一本,該書是學習熱門框架 Spring Boot的經典之作。你不再需要依靠運氣,而是勤奮。截止統計日期2020年6月30日,統計數據以官方公眾號工具為準,運營人員不參加活動,本次活動圖書由掘金社區贊助。

1. 前言

前幾天講了設計模式中的命令模式,今天來看看另一個模式。移動支付目前在國內已經是非常普及了,連樓下早餐攤的七十多歲大媽也使用支付寶和微信支付賣雞蛋餅。如果讓你做一個App你肯定要考慮多個渠道支付,以保證獲客渠道。如果讓你來接入多種支付渠道你會怎麼設計?

2. 通常寫法

一般下面這種寫法很容易被創造出來:

    public boolean pay(BigDecimal amount){
        
        boolean ret =false;
        if (alipay){
            //todo 支付寶的邏輯
        }else if (wechatpay){
            //todo 微信支付的邏輯
        }else if (ooxx){
           // …… 
        }
        return ret;
    }

如果集成了四五種支付,這個代碼就沒法看了少說幾千行,而且改動某個支付的邏輯很容易改了其它支付的邏輯。因此需要合理的設計來避免這種風險。

3. 策略模式

大部分的支付可以簡化為這個流程:

中間的發起支付前邏輯支付后處理邏輯是客戶端的自定義業務邏輯,向支付服務器發送的請求只會攜帶對應支付服務器特定要求的參數調用不同的支付SDK。所以我們分別建立對應支付方式的策略來隔離區分它們,降低它們的耦合度。當準備支付時我們只需要選擇對應的策略就可以了。

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

這就用到了設計模式中的策略模式:

結合上面的類圖,我們就來結合著需求來聊聊策略模式中的主要幾個角色。

  • Strategy接口。這個接口用來聲明每一種方式的獨立執行策略,用來封裝具體策略的特有算法邏輯。
  • ConcreteStrategyStrategy的實現類。實現了不同策略的算法邏輯。比如每種支付的調用細節等等。
  • Context上下文。它通過策略接口來引用了具體的策略並使用具體的策略來執行邏輯,同時所有策略的共性也可以在該類中進行統一處理。在聚合支付需求中我們傳入一個策略,先執行支付前的邏輯,然後使用策略,策略執行完畢后,再執行後置的共性邏輯。
  • Client客戶端。創建策略對象並傳遞給上下文Context,然後由上下文運行具體的策略。

結合業務邏輯是這樣的:請求到達客戶端,客戶端根據請求中包含的支付渠道來構建對應的策略對象並把它交給上下文對象去執行支付流程。

然後我們就可以分別為支付寶、微信支付、銀聯支付構造三個策略對象 AliPayStrategyWechatPayStrategyUnionPayStrategy ,我們來模擬一下執行策略:

public class Client {

    public static void main(String[] args) {
        // 獲取請求中的支付渠道標識
        String code = "p01";
        PayStrategy payStrategy = null;
        PayRequest request = null;
        
        if (PayType.ALI.getCode().equals(code)) {
            //組裝為支付寶支付策略
            payStrategy = new AliPayStrategy();
            // 構造支付寶請求參數
            request = new AliPayRequest();
        }
        if (PayType.WECHAT.getCode().equals(code)) {
            //組裝為微信支付策略
            payStrategy = new AliPayStrategy();
            // 構造微信支付請求參數
            request = new WechatPayRequest();
        }

        if (PayType.UNION.getCode().equals(code)) {
            //組裝為銀聯支付策略
            payStrategy = new UnionPayStrategy();
            // 構造銀聯支付請求參數
            request = new UnionPayRequest();
        }

        if (Objects.nonNull(payStrategy)) {
            PayContext payContext = new PayContext();
            payContext.setPayStrategy(payStrategy);
            payContext.pay(request);
        }
    }
}

我們拿到請求中的支付標識,然後初始化對應的支付策略,封裝指定的請求參數,交給上下文對象PayContext 來執行請求。如果你再增加什麼ApplePay之類的去實現ApplePayStrategy就行了。

4. 優缺點

策略模式並不都帶來正面的作用。

4.1 優點

  • 我們將算法的實現和算法的使用進行了隔離,算法實現只關心算法邏輯,使用算法只關心什麼條件下使用什麼算法。
  • 開閉原則,無需修改上下文對象,想擴展只需要引入新的策略。
  • 運行時根據條件動態的去切換算法。
  • 適應個性的同時也可以兼容共性。

4.2 缺點

  • 客戶端必須明確知道激活策略的條件。
  • 引入太多的策略類。
  • 可被一些函數式接口所代替。偽代碼Pay.request(data).strategy(data->{ })

5. 總結

策略模式也是很常見而且有着廣泛使用場景的設計模式。今天我們從聚合支付來學習了策略模式,對它的優缺點也進行了一個分析。隨着函數式編程的普及,策略模式開始被逐漸的代替,但是它依然值得我們去學習。感謝你的閱讀,多多關注:碼農小胖哥,更多編程乾貨奉上。

關注公眾號:Felordcn 獲取更多資訊

個人博客:https://felord.cn

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

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

Spring Boot入門系列(十四)使用JdbcTemplate操作數據庫,配置多數據源!_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

前面介紹了Spring Boot 中的整合Mybatis並實現增刪改查、如何實現事物控制。不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html。

Spring Boot 除了Mybatis數據庫框架,還有JdbcTemplate等數據庫操作框架,同樣也比較簡單實用,如果是一般簡單的項目,用JdbcTemplate完全可以實現相關的數據庫操作。它雖然沒有MyBatis功能強大,但使用比較簡單,JdbcTemplate應該算是最簡單的數據持久化方案,所以下面就來給大家介紹Spring Boot 使用JdbcTemplate操作數據庫,配置多數據源!

 

一、JDBC簡介

JDBC(Java Data Base Connectivity, Java 數據庫連接)是一種用於執行各種數據庫操作的 API,可以為多種數據庫提供統一訪問接口。所以,JDBC 就像是一套 Java 訪問數據庫的 API 規範,利用這套規範屏蔽了各種數據庫 API 調用的差異性。當應用程序需要訪問數據庫時,調用 JDBC API 相關代碼進新操作,再由JDBC調用各類數據庫的驅動包進行數據操作,最後數據庫驅動包和對應的數據庫通訊協議完成對應的數據庫操作。

在Java領域,數據持久化有幾個常見的方案,有Spring Boot自帶的JdbcTemplate、有MyBatis,還有JPA,在這些方案中,最簡單的就是Spring Boot自帶的JdbcTemplate,雖然沒有MyBatis功能強大,但是,使用比較簡單,事實上,JdbcTemplate應該算是最簡單的數據持久化方案。

 

二、快速開始

開始之前,需要創建一個Spring Boot項目,JdbcTemplate的引用很簡單,開發者在創建一個SpringBoot項目時,選上Jdbc以及數據庫驅動依賴即可。之前介紹過如何創建項目這裏就不介紹,直接使用之前創建的項目工程。

1、依賴配置

1、pom添加依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

需要注意的是

如果是用數據庫連接池,記得添加Druid數據庫連接池依賴。

這裏可以添加專門為Spring Boot打造的druid-spring-boot-starter,JdbcTemplate默認使用Hikari 連接池,如果需要使用druid,需要另外配置。

 

2、application.properties配置數據源

接下來需要在application.properties中提供數據的基本配置即可,如下:

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

注意:在 Spring Boot 2.1.0 中, com.mysql.jdbc.Driver 已經過期,推薦使用com.mysql.cj.jdbc.Driver

如此之後,所有的配置就算完成了,接下來就可以直接使用JdbcTemplate了,是不是特別方便。其實這就是SpringBoot的自動化配置帶來的好處。

 

2、數據庫和實體類

1、數據庫表

DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
    `name` varchar(32) DEFAULT NULL COMMENT '名稱',
    `code` varchar(32) DEFAULT NULL COMMENT '編碼',
    `price` int DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

2、實體類

package com.weiz.pojo;

public class Product {
    private Long id;
    private String name;
    private String code;
    private int price;
    public Product(String name, String code, int price) {
        this.name = name;
        this.code = code;
        this.price = price;
    }
    // 省略 getter setter
}

實體類的數據類型要和數據庫字段一一對應,否則會有問題。

3、Serverice封裝

創建ProductService和ProductServiceImpl類

1、創建 UserService 定義我們常用的增刪改查接口

package com.weiz.service;

import com.weiz.pojo.Product;

public interface ProductService {
    int save(Product product);

    int update(Product product);

    int delete(long id);

    Product findById(long id);
}

2、創建 ProductServiceImpl 類實現 ProductService 類接口

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

package com.weiz.service.impl;

import com.weiz.pojo.Product;
import com.weiz.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;


@Service
public class ProductServiceImpl implements ProductService  {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int save(Product product) {
        return jdbcTemplate.update("INSERT INTO products(name, code, price) values(?, ? , ?)",
                product.getName(), product.getCode(), product.getPrice());
    }

    @Override
    public int update(Product product) {
        return jdbcTemplate.update("UPDATE products SET name = ? , code = ? , price = ? WHERE id=?",
                product.getName(), product.getCode(), product.getPrice(), product.getId());
    }

    @Override
    public int delete(long id) {
        return jdbcTemplate.update("DELETE FROM products where id = ? ",id);
    }

    @Override
    public Product findById(long id) {
        return jdbcTemplate.queryForObject("SELECT * FROM products WHERE id=?", new Object[] { id }, new BeanPropertyRowMapper<Product>(Product.class));
    }

}

代碼說明:

UserServiceImpl類上使用 @Service 註解用於標註數據訪問組件,@Autowired 在類中注入 JdbcTemplate,JdbcTemplate是 Spring Boot操作JDBC 提供的工具類 。

除了以上這些基本用法之外,JdbcTemplate也支持其他用法,例如調用存儲過程等,這些都比較容易,而且和Jdbc本身都比較相似,這裏也就不做介紹了,有興趣可以留言討論。

 

三、調用測試

 接下來我們對jdbc操作數據庫的功能進行測試。

1、創建ProductController 

package com.weiz.controller;

import com.weiz.pojo.Product;
import com.weiz.service.ProductService;
import com.weiz.utils.JSONResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("product")
public class ProductController {
    @Autowired
    private ProductService productService;


    @RequestMapping("/save")
    public JSONResult save() {
        Product product = new Product();
        product.setCode("iphone 11");
        product.setName("iphone 11");
        product.setPrice(100);
        productService.save(product);
        return JSONResult.ok("保存成功");
    }

    @RequestMapping("/update")
    public JSONResult update() {
        long pid = 1;
        Product product = new Product();
        product.setCode("iphone 12");
        product.setName("iphone 12");
        product.setPrice(200);
        product.setId(pid);
        productService.update(product);
        return JSONResult.ok("修改成功");
    }

    @RequestMapping("/delete")
    public JSONResult delete(long pid) {
        productService.delete(pid);
        return JSONResult.ok("刪除成功");
    }

    @RequestMapping("/findbyId")
    public JSONResult findById(long pid) {
        Product product =  productService.findById(pid);
        return JSONResult.ok(product);
    }
}

2、啟動項目,在瀏覽器分別輸入增刪改查對應的地址,測試對應的方法是不是正確即可。

 

四、多數據源的使用

在實際項目中,經常會碰到使用多個數據源的情況, 比如:需要使用多個host、需要使用多種數據庫(MySql、Oracle、SqlServer…)。SpringBoot中,對此都有相應的解決方案,不過一般來說,如果有多數據源的需求,我還是建議首選分佈式數據庫中間件MyCat。這些都是比較成熟的框架,不需要自己重新寫一套。當然如果一些簡單的需求,還是可以使用多數據源的,Spring Boot中,JdbcTemplate、MyBatis以及Jpa都可以配置多數據源。接下來,就在上面的項目的基礎上進行改造,給大家介紹JdbcTemplate 如何配置多數據源。

1、配置多數據源

application.properties配置多個數據源:

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

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

上面的配置文件,添加了兩個數據源,一個是 zwz_test 庫,鈴個是 zwz_test2 庫。

注意:之前單個數據源的數據庫連接是:spring.datasource.url,這裏多個數據源使用的是 spring.datasource.*.jdbc-url,因為JdbcTemplate默認使用Hikari 連接池,而 HikariCP 讀取的是 jdbc-url 。

 

2、配置JDBC初始化

創建DataSourceConfig,在項目啟動的時候讀取配置文件中的數據庫信息,並對 JDBC 初始化。  

package com.weiz.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name="primaryJdbcTemplate")
    public JdbcTemplate primaryJdbcTemplate (
            @Qualifier("primaryDataSource") DataSource dataSource ) {
        return new JdbcTemplate(dataSource);
    }
    @Bean(name="secondaryJdbcTemplate")
    public JdbcTemplate secondaryJdbcTemplate(
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

DataSourceConfig類的作用是在項目啟動的時候根據特定的前綴加載不同的數據源,再根據構建好的數據源創建不同的 JDBC。   
注意事項:使用多個數據源時,需要添加@Primary註解,@Primary:自動裝配時當出現多個Bean候選者時,被註解為@Primary的Bean將作為首選者。Primary 意味着”主要的”,類似與SQL語句中的”primary key”,有且只能有一個,否則會報錯。     3、修改Serverice封裝 需要對 ProductServerice 中的所有方法法進行改造,增加一個傳入參數 JdbcTemplate,根據調用方傳入的JdbcTemplate 進行操作。

// ProductService 接口
public interface ProductService {
    int save(Product product, JdbcTemplate jdbcTemplate);
    // 省略其他方法  
}

// ProductServiceImpl 
@Service
public class ProductServiceImpl implements ProductService  {
    @Override
    public int save(Product product,JdbcTemplate jdbcTemplate) {
        return jdbcTemplate.update("INSERT INTO products(name, code, price) values(?, ? , ?)",
                product.getName(), product.getCode(), product.getPrice());
    }
    // 省略其他方法 
}

 

4、調用測試

同樣,將之前的ProductController 修改如下:

@RestController
@RequestMapping("product")
public class ProductController {
    @Autowired
    private ProductService productService;
    @Autowired
    private JdbcTemplate primaryJdbcTemplate;
    @Autowired
    private JdbcTemplate secondaryJdbcTemplate;

    @RequestMapping("/save")
    public JSONResult save() {
        Product product = new Product();
        product.setCode("iphone 11");
        product.setName("iphone 11");
        product.setPrice(100);
        productService.save(product,primaryJdbcTemplate);
        productService.save(product,secondaryJdbcTemplate);
        return JSONResult.ok("保存成功");
    }

    // 省略其他方法
}

在瀏覽器中輸入:/save 地址后,查看zwz_test 和 zwz_test2數據庫中的products表,都存入一條數據,說明多數據源插入數據成功,其他方方法也是一樣的。這樣在實際項目中,我們通過傳入不同的JdbcTemplate 實例,就可以操作多個數據庫。

 

最後

以上,就把Spring Boot 使用jdbcTemplate 操作數據庫介紹完了。同時也介紹了如何配置使用多數據源。Spring Boot 項目中 JDBC 操作數據庫是不是非常簡單。

這個系列課程的完整源碼,也會提供給大家。大家關注我的微信公眾號(架構師精進),回復:springboot源碼。獲取這個系列課程的完整源碼。

 

 

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

LeetCode 73,為什麼第一反應想到的解法很有可能是個坑?_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

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

今天是LeetCode第42篇文章,我們來看看LeetCode第73題矩陣置零,set matrix zeroes。

這題的難度是Medium,通過率在43%左右,從通過率上可以看出這題的難度並不大。但是這題的解法不少,從易到難,有很多種方法。而且解法和它的推導過程都挺有意思,我們一起來看下。

題意

首先我們來看題意,這題的題意很簡單,給定一個二維數組。要求我們對這個數組當中的元素做如下修改,如果數組的i行j列為0,那麼將同行和同列的元素全部置為0。要求我們直接在原數組上進行修改,而不是返回一個新的數組。

言下之意,要求我們實現一個in-place的方法,而避免額外開闢新的內存。

樣例

Input: 
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
Output: 
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]

近在眼前的解法原來是坑

這題的題意非常簡單,解法也非常明顯,以至於很多人拿到它都會當做模擬題來解決。即遍歷一下數組,如果找到0,那麼將它所在的行和列賦值為0,然後繼續遍歷。

這段邏輯並不難寫,我們很容易寫出來:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        for i in range(n):
            for j in range(m):
                # 當我們找到為0的位置之後,將所在的行和列置為0
                if matrix[i][j] == 0:
                    for k in range(m):
                        matrix[i][k] = 0
                    for k in range(n):
                        matrix[k][j] = 0

但是很遺憾的是,這樣的做法是錯誤的,實際上連樣例都無法通過。通不過樣例的原因也很簡單, 因為0具有傳遞性。

舉個簡單的例子,假設第0行當中有一個0,那麼最後的結果一定是第0行全部被置為0。但問題是我們是在遍歷到0的時候來進行的set操作,這樣會將第0行的其他元素也置為0。這樣當我們遍歷到後面的位置之後,會繼續傳遞,從而將一些不該置為0的地方也置為0了。

舉個簡單的例子,比如:第0行是1 0 0 1。顯然由於第0行存在0,所以操作之後的結果一定是全為0。但問題是matrix[0][3]這個位置原本並不為0,但是如果我們在發現matrix[0][1]為0的時候,將它置為0的話,那麼當我們後面遍歷到matrix[0][3]得到0的時候,會無法判斷究竟是這個位置原本就是0,還是前面出現了0導致這一行全部變成了0。這兩者的操作是不同的。

眼看着目標就在眼前,好像一伸手就碰得到,但是偏偏好像這一步就是咫尺天涯,怎麼也碰不到。這種感覺想想都很難受,我想,當你試着用這種方法去解這道題然後發現不行的時候,一定會有這樣的感覺。並且你會發現好像也沒有什麼很好的辦法來優化。

這種情況在正式的算法比賽當中經常遇到,所以專業的競賽选手有了經驗(吃過虧)之後,想出思路的第一時間就會立即轉向思考,這樣做是不是會有什麼坑,或者是考慮不到的情況。嚴謹一點的同學還會構思幾組不同的測試數據進行測試,或者是腦海中模擬算法的運算。

剛不過去只能繞

以前我年輕的時候總是不信邪,有時候明知道這個方法並不好,或者是存在反例,但是仍會堅持想要通過自己的努力想出一個方案來解決它,而不是更換方法。

我不知道有多少人有同樣的想法,但是一般來說頭鐵的毛病最後總是會被治好的。這題算是一個不錯的例子,如果你堅持使用模擬的方法來做這道題,只有一種方案就是再創建一個同樣大小的數組來作為緩存。當我們遇到0的時候,我們不直接修改原數組中的結果,而是修改緩存,將同行和同列緩存數組中的元素置為0,最後再將緩存數組與原數組合併。

但是顯然這不是一種好的方法,因為題目要求in-place的目的就是為了節約空間,我們另外創建了一個同樣大小的數組顯然違背了題目的本意

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

所以頭鐵到最後還是得認清現狀,這個方法不適合這道題,需要更換解法。如果是在比賽當中得出的這個結論,那麼很有可能獎牌已經和你沒什麼關係了。堅持和固執本身也許沒有太大的區別, 可能只是出現的場景不一樣。

進階解法

回到這道題本身,我們已經證明了模擬的思路是行不通的,除了一邊遍歷一邊操作可能帶來的混亂之外,還有一個點是這樣的複雜度很高。因為如果原數據當中如果本身0就很多的話,那麼我們會需要不停地操作,極端情況下,如果所有元素都是0,那麼我們每一個位置都需要操作一下行列,整體的複雜度會達到

既然如此,還有什麼好的辦法嗎?

當然是有的,其實也挺明顯的,因為對於一個出現的0來說它會影響的範圍是固定的,就是所在的行和列,那我們是不是記錄下會全部置為0的行和列,最後再遍歷一遍數據,看下當前元素是不是出在置為0的範圍當中就可以了。這種方法需要我們再創建兩個數組,用來存儲行和列是否被置為0。

這個解法也很直觀,想到了代碼應該不難寫:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        rows = [0 for _ in range(n)]
        cols = [0 for _ in range(m)]
        
        # 記錄置為0的行和列
        for i in range(n):
            for j in range(m):
                if matrix[i][j] == 0:
                    rows[i], cols[j] = 1, 1
                    
        # 如果所在行或者列置為0,那麼當前位置為0
        for i in range(n):
            for j in range(m):
                if rows[i] or cols[j]:
                    matrix[i][j] = 0
                    

終極解法

上面的做法雖然通過之後的戰績不太光彩,沒能戰勝90%以上的提交,但是能夠通過,而且算法沒有數量級的差距,也算是可以的。如果讓我來做,我可能就想到這種方法為止了。但是題目當中明確說了,還有空間複雜度為O(1)的算法,逼得我進一步思考了一下。

一般來說我們都是優化時間複雜度,很少會優化空間複雜度。相比於優化時間,優化空間有時候更加困難。因為有些時候我們可以空間換時間,可以預處理,可以離線計算……方法相對比較多。但優化空間的方法則很少,尤其是很多時候還不能犧牲時間,所以一般來說只能從算法本身來優化,很少有什麼套路可以套用。

在這個問題當中,要優化空間複雜度到常數級,那麼說明我們連數組都不能用。也就是說不能記錄行和列的信息,但是我們也不能用模擬的方法來進行,那麼應該怎麼辦呢?

干想是很難想出來的, 但是我們換個思路,問題就完全不一樣了。上面的算法時間複雜度是最優的,空間複雜度不太行,那麼有沒有辦法既使用同樣的算法,又能節省空間呢?看起來似乎不可能,但是其實可以,方法說穿了也並不值錢,就是將數據想辦法存在已有的地方,而不是另外開闢空間。在這個問題當中,已有的地方當然就只有一個就是原數組。也就是說我們要把每一行和列是否為0的信息記錄在原數組當中,比如我們可以把第0行和第0列用來做這個事情。

但這樣又會帶來另外一個問題,如果第0行和第0列本身當中也有0出現該怎麼辦?沒辦法,只能特判了。我們單獨用變量來記錄第0行和第0列是否被置為0,這樣我們就最大化地利用了空間,將空間複雜度降低到了常數級。

代碼邏輯和上面一脈相承,只是多了一點騷操作。

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        
        row, col = False, False
        
        # 特判0,0的位置
        if matrix[0][0] == 0:
            row, col = True, True
        
        # 特判第0列是否含0
        for i in range(n):
            if matrix[i][0] == 0:
                col = True
                break
                
        # 特判第0行是否含0
        for i in range(m):
            if matrix[0][i] == 0:
                row = True
                break
                
        # 將i行,j列是否為0的信息存入matrix當中
        for i in range(0, n):
            for j in range(0, m):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0
                    matrix[0][j] = 0
                    
        for i in range(1, n):
            for j in range(1, m):
                # 根據第0行與第0列數據還原
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0
                  
        # 最後處理第0行與第0列
        if row:
            for i in range(m):
                matrix[0][i] = 0
                
        if col:
            for i in range(n):
                matrix[i][0] = 0

總結

到這裏,這道題就算是分享完了,它的題意簡單,但是解法挺多的,我個人感覺也許還存在更好的解法也不一定。

我個人做完這題最大的感受不是這題的思路如何,也不是它涉及的算法如何,而是想到了很多和算法題無關的事情。比如我們生活當中有沒有這樣看似簡單,但是做起來發現一點也不簡單的事情?有沒有眼看着目標就在眼前,卻發現選擇的路一開始就是錯的呢?

帶着這樣的思路來做題,會發現題目也變得有意思多了。

今天的內容就是這些,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

從一年前的1200多人優化到現在200多人,待在這樣的技術團隊是一種什麼體驗?_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

 聲明:

感覺之前可能沒表達清楚,導致評論區很多夥伴誤會了,我依然在職,只是身邊的朋友一個個的離開讓我有很大的觸動,所以才寫下這篇文章,以下所有內容均為個人感受與意見,與公司無關

1.寫點情懷

  平日里都是分享技術的,但是最近做的最多的一件事卻是送別,挺悲傷的一個詞,我個人不太喜歡,但是現在整個大的環境都不太景氣,眾多企業紛紛倒閉,一批批工人,白領被迫失去了工作,其中也不乏我自己所在的職業:程序員。特別的從去年年底至今,依舊活着的公司大部分也是苟延殘喘,大幅度的“優化”員工,而我自己身邊的朋友,送走了一批又一批,自己的感觸挺深的,所以想把這段時間的經歷寫成文字,當做是解壓了。

 

2.親身經歷

  在現在這家公司幹了接近三年了,剛進來時,整個IT部分是700多人,當時業務發展速度很快,很多業務線需要技術做數據支撐,那時公司處在上升期,所以不斷的在擴張技術團隊去滿足各種業務需求,頂峰時期,技術團隊達到了1200多人,各種項目也是琳琅滿目(姑且這樣形容吧,算是對好的一種嚮往),自己也有參与到不同的項目中,也學到了挺多東西,交到了很多朋友,樂此不疲。但是好景不長,伴隨着互聯網寒冬以及疫情的到來,從去年年底到現在,身邊很多朋友陸陸續續都離開了,舉一個印象比較深刻的例子:

  去年五月份開始,我們領導找我組建一個新的項目團隊,做3D應用相關的開發,當時結合了公司的業務和市場反饋,經過簡短溝通后說干就干,然後就開始對相關技術做調研(因為在這之前完全沒做過3D應用的開發),剛開始只招聘了一個技術,之前有做過相關工作,來了之後形成了一個三人小組,開始做硬件選型,做競品分析(當然也在學習別人的優勢),做關鍵技術的攻克,兩個月後領導拿着我們費了九年二虎之力的一個demo去找老總,估計就是一頓前(連)景(哄)展(帶)示(騙)了,項目被公司看好,覺得可以投入人力和更多的資源,將其形成真正的產品。然後就開始招聘技術,產品,3D建模師,UI設計師等等搭建技術團隊,到9月份形成了一個9人組成的項目小組開始立項,走產品化流程,從一個demo到真正形成一個產品確實也需要走很多路,由於應用較為特殊,我們從硬件開始準備,期間用到的技術棧(包含但又不僅僅是):電機,樹莓派,Python,Nodejs,.NetCore,Unity3D,Aws眾多雲服務(MySQL,Redis,SQS,負載均衡器,CDN,S3,EC2等等),過程可以用過五關斬六將來形容,解決了很多技術上的難點,也做了一些以前沒有做過的嘗試,直到12月份項目上線可第一個版本,大家都覺得鬆了一口氣,畢竟過程雖然很波折,但是大家都很充實,一起擼代碼,一起加班,一起解決困難,一起喝下午茶,一起打鬧,有時會為一個問題爭執不休而鬧情緒,有時也會因為一點點小的突破而哈哈大笑,確實留下了很多珍貴的回憶。上線后也收到了很多反饋,後續也在對線上版本進行不斷的迭代優化,從上線到過年一個多月的時間里,一直頻繁的在發版本,雖然大家都覺得被各方大佬孽的很慘,但是依然幹勁十足。年後由於疫情,大家都開始在家遠程辦公,但是面對一次次的延遲上班的通知,大家可能心裏多少都有些想法,但是該來的總會來,遠程辦公的第二周,領導就通知說,現在疫情給公司帶來了較大的影響,為了維護公司的正常運轉只保留核心技術團隊,而我們剛上線兩個多月的項目也被迫就此夭折。而我也親自參与到了項目的下線整個過程,心裏很不是滋味,畢竟就像是十月懷胎后剛出生的嬰兒,還希望它能健康長大的,但是深深的明白,在職場沒有人會去談這些感情感性的東西,都是以結果,收益為導向。

  項目被砍了,人員自然也是公司關注的焦點,各種名義開始大幅度的優化人員,而我所在的項目組,除了我和我領導兩個人轉到了另外一個團隊,其他所有的小夥伴都開始了離開的行程,收到了很多工作交接,送他們一個個的離開,這種場景雖然嘴上會說沒關係,走到哪裡都是朋友,但是內心裏多少還是有點不舒服,畢竟一起留下了諸多回憶,難免會有所傷感。而這個例子只是N多個項目小組的一個縮影,由於在公司做過很多項目,認識的人也不少,最近收到他們的消息,陸陸續續的都離開了,再放眼望去,三年在技術團隊認識的一些人現在依然在的,寥寥無幾了,也是最觸動自己的地方。

 

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

3.大環境不好,怎麼破局?

  3.1 不管程序員是不是吃青春飯,相對其他行業來說,程序員的薪資還是有優勢的,就我們公司,開發年薪50W的不在少數,由開發上升為管理崗的年薪過100W的也是存在的,哪怕是青春飯,那麼是否能在有限的時間裏面提升自己,讓自己更有價值,也能為以後打下好的基礎。

  3.2 互聯網更新迭代的速度是很快的,幾年就會興起一股潮流趨勢,一會人工智能,一會區塊鏈,一會中台等等,很多開發者會去追隨這些比較流行的物種,當然了解前沿知識是好事,但是不在少數的開發者會盲目的去跟風,一會做前端,一會做後端,一會小程序,一會人工智能,各種技術棧說起來都會,都用過,但是不精,知其然,不知其所以然。並不是說了解這些東西不好,只是我們要有個度,不能盲目,一味追隨可能讓自己浮躁,忽略了知識體系的積累,從而使自己失去了核心競爭力。

  3.3 之前面試我自己有個習慣,在結束時一般面試官會問自己有沒有什麼是想了解的,這個時候我通常會問:咱們公司的核心競爭力是什麼?其實這句話用在我們自己身上也很合適,我們自己的核心競爭力,優勢又是什麼呢。我覺得很重要的一點就是不斷學習,快速成長,只有當我們自己保持一個較快的成長速度,那就可以跟別人拉開差距,並持續將差距最大化,這樣我們才能在競爭中保證自身的優勢,最差也能立於不敗之地。

  3.4 如果你現在的工作很安逸,通常都不是什麼好事,這意味着一切按部就班,然後慢慢在消耗我們的上進心和鬥志,扼殺我們的創造力,這也是經常大家都懂的溫水煮了青蛙的道理;同時也意味着我們慢慢失去了競爭力,也會因為太安逸,讓我們變得膽怯,以至於在遇到更好的機會時,我們會猶豫不決… 職場上得懂得居安思危。

 

4.共勉

  希望所有的程序員們也包括自己,做一個有擔當,有理想,有抱負的好青年,帶着寫程序的初心,從“hello world”寫到”change the world”,一起加油吧!

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

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

【大廠面試02期】Redis過期key是怎麼樣清理的?_貨運

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

【大廠面試02期】Redis過期key是怎麼樣清理的?

在Redis中,對於過期key的清理主要有惰性清除,定時清理,內存不夠時清理三種方法,下面我們就來具體看看這三種清理方法。

(1)惰性清除

在訪問key時,如果發現key已經過期,那麼會將key刪除。

(2)定時清理

Redis配置項hz定義了serverCron任務的執行周期,默認每次清理時間為25ms,每次清理會依次遍歷所有DB,從db隨機取出20個key,如果過期就刪除,如果其中有5個key過期,那麼就繼續對這個db進行清理,否則開始清理下一個db。

(3)內存不夠時清理

當執行寫入命令時,如果發現內存不夠,那麼就會按照配置的淘汰策略清理內存,淘汰策略一般有6種,Redis4.0版本后又增加了2種,主要由分為三類

  • 第一類 不處理,等報錯(默認的配置)

    • noeviction,發現內存不夠時,不刪除key,執行寫入命令時直接返回錯誤信息。(Redis默認的配置就是noeviction)
  • 第二類 從所有結果集中的key中挑選,進行淘汰

    • allkeys-random 就是從所有的key中隨機挑選key,進行淘汰
    • allkeys-lru 就是從所有的key中挑選最近使用時間距離現在最遠的key,進行淘汰
    • allkeys-lfu 就是從所有的key中挑選使用頻率最低的key,進行淘汰。(這是Redis 4.0版本后新增的策略)
  • 第三類 從設置了過期時間的key中挑選,進行淘汰

    這種就是從設置了expires過期時間的結果集中選出一部分key淘汰,挑選的算法有:

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

    • volatile-random 從設置了過期時間的結果集中隨機挑選key刪除。

    • volatile-lru 從設置了過期時間的結果集中挑選上次使用時間距離現在最久的key開始刪除

    • volatile-ttl 從設置了過期時間的結果集中挑選可存活時間最短的key開始刪除(也就是從哪些快要過期的key中先刪除)

    • volatile-lfu 從過期時間的結果集中選擇使用頻率最低的key開始刪除(這是Redis 4.0版本后新增的策略)

LRU算法

LRU算法的設計原則是如果一個數據近期沒有被訪問到,那麼之後一段時間都不會被訪問到。所以當元素個數達到限制的值時,優先移除距離上次使用時間最久的元素。

可以使用雙向鏈表Node+HashMap<String, Node>來實現,每次訪問元素后,將元素移動到鏈表頭部,當元素滿了時,將鏈表尾部的元素移除,HashMap主要用於根據key獲得Node以及添加時判斷節點是否已存在和刪除時快速找到節點。

PS:使用單向鏈表能不能實現呢,也可以,單向鏈表的節點雖然獲取不到pre節點的信息,但是可以將下一個節點的key和value設置在當前節點上,然後把當前節點的next指針指向下下個節點,這樣相當於把下一個節點刪除了

//雙向鏈表
    public static class ListNode {
        String key;//這裏存儲key便於元素滿時,刪除尾節點時可以快速從HashMap刪除鍵值對
        Integer value;
        ListNode pre = null;
        ListNode next = null;
        ListNode(String key, Integer value) {
            this.key = key;
            this.value = value;
        }
    }

    ListNode head;
    ListNode last;
    int limit=4;
    
    HashMap<String, ListNode> hashMap = new HashMap<String, ListNode>();

    public void add(String key, Integer val) {
        ListNode existNode = hashMap.get(key);
        if (existNode!=null) {
            //從鏈表中刪除這個元素
            ListNode pre = existNode.pre;
            ListNode next = existNode.next;
            if (pre!=null) {
               pre.next = next;
            }
            if (next!=null) {
               next.pre = pre;
            }
            //更新尾節點
            if (last==existNode) {
                last = existNode.pre;
            }
            //移動到最前面
            head.pre = existNode;
            existNode.next = head;
            head = existNode;
            //更新值
            existNode.value = val;
        } else {
            //達到限制,先刪除尾節點
            if (hashMap.size() == limit) {
                ListNode deleteNode = last;
                hashMap.remove(deleteNode.key);
              //正是因為需要刪除,所以才需要每個ListNode保存key
                last = deleteNode.pre;
                deleteNode.pre = null;
                last.next = null;
            }
            ListNode node = new ListNode(key,val);
            hashMap.put(key,node);
            if (head==null) {
                head = node;
                last = node;
            } else {
                //插入頭結點
                node.next = head;
                head.pre = node;
                head = node;
            }
        }

    }

    public ListNode get(String key) {
        return hashMap.get(key);
    }

    public void remove(String key) {
        ListNode deleteNode = hashMap.get(key);
        ListNode preNode = deleteNode.pre;
        ListNode nextNode = deleteNode.next;
        if (preNode!=null) {
            preNode.next = nextNode;
        }
        if (nextNode!=null) {
            nextNode.pre = preNode;
        }
        if (head==deleteNode) {
            head = nextNode;
        }
        if (last == deleteNode) {
            last = preNode;
        }
        hashMap.remove(key);
    }

LFU算法

LFU算法的設計原則時,如果一個數據在最近一段時間被訪問的時次數越多,那麼之後被訪問的概率會越大,基本實現是每個數據都有一個引用計數,每次數據被訪問后,引用計數加1,需要淘汰數據時,淘汰引用計數最小的數據。在Redis的實現中,每次key被訪問后,引用計數是加一個介於0到1之間的數p,並且訪問越頻繁p值越大,而且在一定的時間間隔內,
如果key沒有被訪問,引用計數會減少。

最後

大家有什麼想法,歡迎進群一起討論(因為大群已經滿200人了,大家可以掃碼進這個小群,我拉大家進大群)!本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,如果想要了解更多大廠面試相關的內容,了解更多可以看
http://notfound9.github.io/interviewGuide/#/docs/BATInterview

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

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

[C#.NET 拾遺補漏]01:字符串操作_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

字符串操作在任意編程語言的日常編程中都隨處可見,今天來匯總一下 C# 中關於字符串的一些你可能遺忘或遺漏的知識點。

逐字字符串

在普通字符串中,反斜杠字符是轉義字符。而在逐字字符串(Verbatim Strings)中,字符將被編程器按照原義進行解釋。使用逐字字符串只需在字符串前面加上 @ 符號。

// 逐字字符串:轉義符
var filename = @"c:\temp\newfile.txt";
Console.WriteLine(filenaame);

// 逐字字符串:多行文本
var multiLine = @"This is a
multiline paragraph."
;
Console.WriteLine(multiLine);

// 非逐字字符串
var escapedFilename = "c:\temp\newfile.txt";
Console.WriteLine(escapedFilename);

輸出:

c:\temp\newfile.txt
This is a
multiline paragraph.
c: emp
ewfile.txt

逐字字符串中唯一不被原樣解釋的字符是雙引號。由於雙引號是定義字符串的關鍵字符,所以在逐字字符串中要表達雙引號需要用雙引號進行轉義。

varstr = @"""I don't think so"", he said.";
Console.WriteLine(str);
// 輸出:"I don't think so", he said.

在逐字字符串中也可以 $ 符號實現字符串內插值。

Console.WriteLine($@"Testing \n 1 2 {5 - 2}");
// 輸出:Testing \n 1 2 3

数字格式化轉換

典型的的格式化方法為:

string.Format("{index[:format]}", number)

可使用“0”和“#”佔位符進行補位。“0” 表示位數不夠位數就補充“0”,小數部分如果位數多了則會四舍五入;“#”表示佔位,用於輔助“0”進行補位。

標準格式化用法:

// “0”描述:佔位符,如果可能,填充位
string.Format("{0:000000}",1234); // 結果:001234

// “#”描述:佔位符,如果可能,填充位
string.Format("{0:######}",1234); // 結果:1234
string.Format("{0:#0####}",1234); // 結果:01234
string.Format("{0:0#0####}",1234); // 結果:0001234

// "."描述:小數點
string.Format("{0:000.000}", 1234); // 結果:1234.000
string.Format("{0:000.000}", 4321.12543); // 結果:4321.125

// ","描述:千分表示
string.Format("{0:0,0}", 1234567); //結果:1,234,567

// "%"描述:格式化為百分數
string.Format("{0:0%}",1234); // 結果:123400%
string.Format("{0:#%}", 1234.125); // 結果:123413%
string.Format("{0:0.00%}",1234); // 結果:123400.00%
string.Format("{0:#.00%}",1234.125); // 結果:123412.50%

內置快捷字母格式化用法:

// E-科學計數法表示
(25000).ToString("E"); // 結果:2.500000E+004

// C-貨幣表示,帶有逗號分隔符,默認小數點后保留兩位,四舍五入
(2.5).ToString("C"); // 結果:¥2.50

// D[length]-十進制數
(25).ToString("D5"); // 結果:00025

// F[precision]-浮點數,保留小數位數(四舍五入)
(25).ToString("F2"); // 結果:25.00

// G[digits]-常規,保留指定位數的有效数字,四舍五入
(2.52).ToString("G2"); // 結果:2.5

// N-帶有逗號分隔符,默認小數點后保留兩位,四舍五入
(2500000).ToString("N"); // 結果:2,500,000.00

// X-十六進制,非整型將產生格式異常
(255).ToString("X"); // 結果:FF

ToString 也可以自定義補零格式化:

(15).ToString("000");              // 結果:015
(15).ToString("value is 0"); // 結果:value is 15
(10.456).ToString("0.00"); // 結果:10.46
(10.456).ToString("00"); // 結果:10
(10.456).ToString("value is 0.0"); // 結果:value is 10.5

轉換為二進制、八進制、十六進制輸出:

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

int number = 15;
Convert.ToString(number, 2); // 結果:1111
Convert.ToString(number, 8); // 結果:17
Convert.ToString(number, 16); // 結果:f

自定義格式化器:

public class CustomFormat : IFormatProvider, ICustomFormatter
{
public string Format(string format, object arg, IFormatProvider formatProvider)
{
if (!this.Equals(formatProvider))
{
return null;
}
if (format == "Reverse")
{
return string.Join("", arg.ToString().Reverse());
}
return arg.ToString();
}

public object GetFormat(Type formatType)
{
return formatType == typeof(ICustomFormatter) ? this : null;
}
}

使用自定義格式化器:

String.Format(newCustomFormat(), "-> {0:Reverse} <-", "Hello World");
// 輸出:-> dlroW olleH <-

字符串拼接

將數組中的字符串拼接成一個字符串:

var parts = new[] { "Foo", "Bar", "Fizz", "Buzz"};
var joined = string.Join(", ", parts);
// joined = "Foo, Bar, Fizz, Buzz"

以下四種方式都可以達到相同的字符串拼接的目的:

string first = "Hello";
string second = "World";
string foo = first + " " + second;
string foo = string.Concat(first, " ", second);
string foo = string.Format("{0} {1}", firstname, lastname);
string foo = $"{firstname} {lastname}";

字符串內插法

簡單用法:

var name = "World";
var str =$"Hello, {name}!";
// str = "Hello, World!"

帶日期格式化:

var date = DateTime.Now;
var str = $"Today is {date:yyyy-MM-dd}!";

補齊格式化(Padding):

var number = 42;

// 向左補齊
var str = $"The answer to life, the universe and everything is {number, 5}.";
// str = "The answer to life, the universe and everything is ___42." ('_'表示空格)

// 向右補齊
var str = $"The answer to life, the universe and everything is ${number, -5}.";
// str = "The answer to life, the universe and everything is 42___."

結合內置快捷字母格式化:

var amount = 2.5;
var str = $"It costs {amount:C}";
// str = "¥2.50"

var number = 42;
var str = $"The answer to life, the universe and everything is {number, 5:f1}.";
// str = "The answer to life, the universe and everything is ___42.1"

參考:

1.《C# 7.0 in a Nutshell》

2. https://bit.ly/2U1eIK9

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

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

Kubernetes筆記(四):詳解Namespace與資源限制ResourceQuota,LimitRange_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

前面我們對K8s的基本組件與概念有了個大致的印象,並且基於K8s實現了一個初步的CI/CD流程,但對裏面涉及的各個對象(如Namespace, Pod, Deployment, Service, Ingress, PVC等)及各對象的管理可能還缺乏深入的理解與實踐,接下來的文章就讓我們一起深入K8s的各組件內部來一探究竟吧。下圖是基於個人的理解梳理的一個K8s結構圖,示例了各個組件(只包含了主要組件)如何協同。

後續幾篇文章圍繞該圖涉及組件進行整理介紹,本文主要探究Namespace及與Namespace管理相關的資源限制ResourceQuota/LimitRange部分。

Namespace

理解

Namespace即命名空間,主要有兩個方面的作用:

  1. 資源隔離:可為不同的團隊/用戶(或項目)提供虛擬的集群空間,共享同一個Kubernetes集群的資源。比如可以為團隊A創建一個Namespace ns-a,團隊A的項目都部署運行在 ns-a 中,團隊B創建另一個Namespace ns-b,其項目都部署運行在 ns-b 中,或者為開發、測試、生產環境創建不同的Namespace,以做到彼此之間相互隔離,互不影響。我們可以使用 ResourceQuota 與 Resource LimitRange 來指定與限制 各個namesapce的資源分配與使用
  2. 權限控制:可以指定某個namespace哪些用戶可以訪問,哪些用戶不能訪問

Kubernetes 安裝成功后,默認會創建三個namespace:

  • default:默認的namespace,如果創建Kubernetes對象時不指定 metadata.namespace,該對象將在default namespace下創建
  • kube-system:Kubernetes系統創建的對象放在此namespace下,我們前面說的kube-apiserver,etcd,kube-proxy等都在該namespace下
  • kube-public:顧名思義,共享的namespace,所有用戶對該namespace都是可讀的。主要是為集群做預留,一般都不在該namespace下創建對象

實踐

1.查看namesapce

kubectl get namespaces
kubectl get namesapce
kubectl get ns               # 三個操作等效
kubectl get ns --show-labels # 显示namespace的label

使用namesapces,namesapce,ns都是可以的。如下列出了當前集群中的所有namespace

[root@kmaster ~]# kubectl get ns
NAME                   STATUS   AGE
default                Active   34d
develop                Active   17d
ingress-nginx          Active   33d
kube-node-lease        Active   34d
kube-public            Active   34d
kube-system            Active   34d
kubernetes-dashboard   Active   31d
pre-release            Active   17d

可以使用 kubectl describe 命令來查看某個namespace的概要信息,如

[root@kmaster ~]# kubectl describe ns default
Name:         default
Labels:       <none>
Annotations:  <none>
Status:       Active

No resource quota.

No resource limits.

2.創建namespace

有兩種方式:通過yaml定義文件創建或直接使用命令創建。

# 方式1. 通過yaml定義文件創建
[root@kmaster ~]# vim test-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: test     # namespace的名稱
  labels:
    name: ns-test
[root@kmaster ~]# kubectl create -f ./test-namespace.yaml  

# 方式2. 直接使用命令創建
[root@kmaster ~]# kubectl create ns test

3.在namesapce中創建對象

# 1. 在yaml中通過metadata.namesapce 指定
[root@kmaster ~]# kubectl get deploy my-nginx -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: test  # 指定namespace
spec:
  ...
# 2. 在命令中通過 -n 或 --namesapce 指定
[root@kmaster ~]# kubectl run dev-nginx --image=nginx:latest --replicas=3 -n test

4.設定kubectl namesapce上下文

kubectl上下文即集群、namespace、用戶的組合,設定kubectl上下文,即可以以上下文指定的用戶,在上下文指定的集群與namespace中進行操作管理。查看當前集群kubectl上下文

# 查看當前kubectl上下文
[root@kmaster ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.40.111:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

可見當前上下文為kubernetes-admin@kubernetes (current-context: kubernetes-admin@kubernetes)。

創建一個kubectl上下文

[root@kmaster ~]# kubectl config set-context test --namespace=test --cluster=kubernetes --user=kubernetes-admin
Context "test" created.

再次執行 kubectl config view 將可以看到上面創建的test上下文。

切換上下文

# 設置當前上下文
[root@kmaster ~]# kubectl config use-context test
Switched to context "test".
# 查看當前所在的上下文
[root@kmaster ~]# kubectl config current-context
test

指定了上下文,後續操作都在該上下文對應的namespace中進行,不需要再顯式指定namespace。在上下文中創建對象

# 在當前上下文中創建對象
[root@kmaster ~]# kubectl run my-nginx --image=nginx:latest --replicas=2
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/my-nginx created
# 查看創建的對象,不需要指定namespace
[root@kmaster ~]# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
my-nginx   2/2     2            2           25m
[root@kmaster ~]# kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-667764d77b-ldb78   1/1     Running   0          24m
my-nginx-667764d77b-wpgxw   1/1     Running   0          24m

刪除上下文

[root@kmaster ~]# kubectl config delete-context test
deleted context test from /root/.kube/config

也可以使用如下命令直接切換默認的namespace

# 將默認namespace設置為test
[root@kmaster ~]# kubectl config set-context --current --namespace=test

5.刪除namesapce

可以使用 kubectl delete ns <namespace名稱> 來刪除一個namesapce,該操作會刪除namespace中的所有內容。

[root@kmaster ~]# kubectl delete ns test

Resource Quota

Resource Quota即資源配額,限定單個namespace中可使用集群資源的總量,包括兩個維度:

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

  1. 限定某個對象類型(如Pod)可創建對象的總數;
  2. 限定某個對象類型可消耗的計算資源(CPU、內存)與存儲資源(存儲卷聲明)總數

如果在 namespace 中為計算資源 CPU 和內存設定了 ResourceQuota,用戶在創建對象(Pod、Service等)時,必須指定 requests 和 limits;如果在創建或更新對象時申請的資源與 namespace 的 ResourceQuota 衝突,則 apiserver 會返回 HTTP 狀態碼 403,以及對應的錯誤提示信息。當集群中總的容量小於各個 namespace 資源配額的總和時,可能會發生資源爭奪,此時 Kubernetes 將按照先到先得的方式分配資源。

對象數量限制

聲明格式為: count/<resource>.<group>, 如下列出各類對象的聲明格式

count/persistentvolumeclaims 
count/services
count/secrets
count/configmaps
count/replicationcontrollers
count/deployments.apps
count/replicasets.apps
count/statefulsets.apps
count/jobs.batch
count/cronjobs.batch
count/deployments.extensions

計算資源限制

定義CPU、內存請求(requests)、限制(limits)使用的總量,包括

  • limits.cpu:namespace中,所有非終止狀態的 Pod 的 CPU 限制 resources.limits.cpu 總和不能超過該值
  • limits.memory:namespace中,所有非終止狀態的 Pod 的內存限制 resources.limits.memory 總和不能超過該值
  • requests.cpu:namespace中,所有非終止狀態的 Pod 的 CPU 請求 resources.requrest.cpu 總和不能超過該值
  • requests.memory:namespace中,所有非終止狀態的 Pod 的 CPU 請求 resources.requests.memory 總和不能超過該值

存儲資源限制

定義存儲卷聲明請求的存儲總量或創建存儲卷聲明數量的限制,包括

  • requests.storage:namespace中,所有存儲卷聲明(PersistentVolumeClaim)請求的存儲總量不能超過該值
  • persistentvolumeclaims:namespace中,可以創建的存儲卷聲明的總數不能超過該值
  • <storage-class-name>.storageclass.storage.k8s.io/requests.storage:namespace中,所有與指定存儲類(StorageClass)關聯的存儲卷聲明請求的存儲總量不能超過該值
  • <storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims:namespace中,所有與指定存儲類關聯的存儲卷聲明的總數不能超過該值

除此之外,還可以對本地臨時存儲資源進行限制定義

  • requests.ephemeral-storage:namespace中,所有 Pod 的本地臨時存儲(local ephemeral storage)請求的總和不能超過該值
  • limits.ephemeral-storage:namespace中,所有 Pod 的本地臨時存儲限定的總和不能超過此值

實踐

查看是否開啟 Resource Quota 支持,默認一般是開啟的。如果沒有,可在啟動 apiserver 時為參數 –enable-admission-plugins 添加 ResourceQuota 配置項。

1.創建ResourceQuota

# 創建namespace
[root@kmaster ~]# kubectl create namespace test
# 編輯ResourceQuota定義文檔
[root@kmaster ~]# vim quota-test.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-test
  namespace: test
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi
    requests.nvidia.com/gpu: 4
    pods: "3"
    services: "6"
# 創建ResourceQuota
[root@kmaster ~]# kubectl apply -f quota-test.yaml
# 查看
[root@kmaster ~]# kubectl get quota -n test
NAME         CREATED AT
quota-test   2020-05-26T10:31:10Z
[root@kmaster ~]# kubectl describe quota quota-test -n test
Name:                    quota-test
Namespace:               test
Resource                 Used  Hard
--------                 ----  ----
limits.cpu               0     4
limits.memory            0     4Gi
pods                     0     3
requests.cpu             0     2
requests.memory          0     2Gi
requests.nvidia.com/gpu  0     4
services                 0     6

或者使用kubectl命令,如

[root@kmaster ~]# kubectl create quota quota-test --hard=count/deployments.extensions=2,count/replicasets.extensions=4,count/pods=3,count/secrets=4 --namespace=test

我們在namespace test中創建了一個ResourceQuota,限制CPU、內存請求為2、2GB,限制CPU、內存限定使用為4、4GB,限制Pod個數為3 等。

我們來嘗試創建一個如下定義的Deployment來測試一下,

# 創建一個測試deploy
[root@kmaster ~]# vim quota-test-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: quota-test-deploy
spec:
 selector:
    matchLabels:
      purpose: quota-test
 replicas: 3
 template:
   metadata:
     labels:
       purpose: quota-test
   spec:
     containers:
     - name: quota-test
       image: nginx
       resources:
         limits:
           memory: "2Gi"
           cpu: "1"
         requests:
           memory: "500Mi"
           cpu: "500m"
[root@kmaster ~]# kubectl apply -f quota-test-deploy.yaml -n test
# 查看pod
[root@kmaster ~]# kubectl get pod -n test
NAME                                 READY   STATUS    RESTARTS   AGE
quota-test-deploy-6b89fdc686-2dthq   1/1     Running   0          3m54s
quota-test-deploy-6b89fdc686-9m2qw   1/1     Running   0          3m54s
# 查看deploy狀態
[root@kmaster ~]# kubectl get deploy quota-test-deploy -n test -o yaml
  message: 'pods "quota-test-deploy-6b89fdc686-rmktq" is forbidden: exceeded quota:
        quota-test, requested: limits.memory=2Gi, used: limits.memory=4Gi, limited:
        limits.memory=4Gi'

replicas: 3定義創建三個Pod副本,但只成功創建了兩個Pod,在deploy的status部分(最後一條命令結果),我們可以看到message提示第三個Pod創建時被拒絕,因為內存已達到限定。我們也可以將limits.memory調整為1Gi,將replicas調整為4,來驗證對Pod個數的限制。可看到最終只起了三個Pod,status部分message提示 pods "quota-test-deploy-9dc54f95c-gzqw7" is forbidden: exceeded quota:quota-test, requested: pods=1, used: pods=3, limited: pods=3

Resource Limit Range

理解

Resource Quota 是對namespace中總體的資源使用進行限制,Resource Limit Range 則是對具體某個Pod或容器的資源使用進行限制。默認情況下,namespace中Pod或容器的資源消耗是不受限制的,這就可能導致某個容器應用內存泄露耗盡資源影響其它應用的情況。Limit Range可以用來限定namespace內Pod(或容器)可以消耗資源的數量。

使用LimitRange對象,我們可以:

  1. 限制namespace中每個Pod或容器的最小與最大計算資源
  2. 限制namespace中每個Pod或容器計算資源request、limit之間的比例
  3. 限制namespace中每個存儲卷聲明(PersistentVolumeClaim)可使用的最小與最大存儲空間
  4. 設置namespace中容器默認計算資源的request、limit,並在運行時自動注入到容器中

如果創建或更新對象(Pod、容器、PersistentVolumeClaim)對資源的請求與LimitRange相衝突,apiserver會返回HTTP狀態碼403,以及相應的錯誤提示信息;如果namespace中定義了LimitRange 來限定CPU與內存等計算資源的使用,則用戶創建Pod、容器時,必須指定CPU或內存的request與limit,否則將被系統拒絕;當namespace總的limit小於其中Pod、容器的limit之和時,將發生資源爭奪,Pod或者容器將不能創建,但不影響已經創建的Pod或容器。

實踐

創建一個測試namespace test-limitrange,

# 創建測試namespace
[root@kmaster ~]# kubectl create namespace test-limitrange
# 切換默認的namespace
[root@kmaster ~]# kubectl config set-context --current --namespace=test-limitrange

創建LimitRange定義文件 lr-test.yaml

apiVersion: v1
kind: LimitRange
metadata:
  name: lr-test
spec:
  limits:
  - type: Container       #資源類型
    max:
      cpu: "1"            #限定最大CPU
      memory: "1Gi"       #限定最大內存
    min:
      cpu: "100m"         #限定最小CPU
      memory: "100Mi"     #限定最小內存
    default:
      cpu: "900m"         #默認CPU限定
      memory: "800Mi"     #默認內存限定
    defaultRequest:
      cpu: "200m"         #默認CPU請求
      memory: "200Mi"     #默認內存請求
    maxLimitRequestRatio:
      cpu: 2              #限定CPU limit/request比值最大為2  
      memory: 1.5         #限定內存limit/request比值最大為1.5
  - type: Pod
    max:
      cpu: "2"            #限定Pod最大CPU
      memory: "2Gi"       #限定Pod最大內存
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi        #限定PVC最大的requests.storage
    min:
      storage: 1Gi        #限定PVC最小的requests.storage

該文件定義了在namespace test-limitrange 中,容器、Pod、PVC的資源限制,在該namesapce中,只有滿足如下條件,對象才能創建成功

  • 容器的resources.limits部分CPU必須在100m-1之間,內存必須在100Mi-1Gi之間,否則創建失敗
  • 容器的resources.limits部分CPU與resources.requests部分CPU的比值最大為2,memory比值最大為1.5,否則創建失敗
  • Pod內所有容器的resources.limits部分CPU總和最大為2,內存總和最大為2Gi,否則創建失敗
  • PVC的resources.requests.storage最大為2Gi,最小為1Gi,否則創建失敗

如果容器定義了resources.requests沒有定義resources.limits,則LimitRange中的default部分將作為limit注入到容器中;如果容器定義了resources.limits卻沒有定義resources.requests,則將requests值也設置為limits的值;如果容器兩者都沒有定義,則使用LimitRange中default作為limits,defaultRequest作為requests值

創建與查看LimitRange,

# 創建LimitRange
[root@kmaster ~]# kubectl apply -f lr-test.yaml
# 查看
[root@kmaster ~]# kubectl describe limits lr-test
Name:                  lr-test
Namespace:             test-limitrange
Type                   Resource  Min    Max  Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---    ---  ---------------  -------------  -----------------------
Container              cpu       100m   1    200m             900m           2
Container              memory    100Mi  1Gi  200Mi            800Mi          1500m
Pod                    cpu       -      2    -                -              -
Pod                    memory    -      2Gi  -                -              -
PersistentVolumeClaim  storage   1Gi    2Gi  -                -              -

我們可以創建不同配置的容器或Pod對象來驗證,出於篇幅不再列出驗證步驟。

總結

本文對K8s的Namespace及針對Namespace的資源限制管理ResourceQuota,LimitRange進行了較為深入的探索,其中ResourceQuota對整個Namespace的資源使用情況進行限制,LimitRange則對單個的Pod或容器的資源使用進行限制。Namespace的權限控制可基於RBAC來實現,後續再單獨進行梳理介紹。

原文地址:http://blog.jboost.cn/k8s4-namespace.html

相關閱讀:

  1. Kubernetes筆記(一):十分鐘部署一套K8s環境
  2. Kubernetes筆記(二):了解k8s的基本組件與概念
  3. Kubernetes筆記(三):Gitlab+Jenkins Pipeline+Docker+k8s+Helm自動化部署實踐(乾貨分享!)

作者:雨歌,一枚仍在學習路上的IT老兵
歡迎關注作者公眾號:半路雨歌,一起學習成長

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

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

Docker 容器優雅終止方案_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

原文鏈接:Docker 容器優雅終止方案

作為一名系統重啟工程師(SRE),你可能經常需要重啟容器,畢竟 Kubernetes 的優勢就是快速彈性伸縮和故障恢復,遇到問題先重啟容器再說,幾秒鐘即可恢復,實在不行再重啟系統,這就是系統重啟工程師的殺手鐧。然而現實並沒有理論上那麼美好,某些容器需要花費 10s 左右才能停止,這是為啥?有以下幾種可能性:

  1. 容器中的進程沒有收到 SIGTERM 信號。
  2. 容器中的進程收到了信號,但忽略了。
  3. 容器中應用的關閉時間確實就是這麼長。

對於第 3 種可能性我們無能為力,本文主要解決 1 和 2。

如果要構建一個新的 Docker 鏡像,肯定希望鏡像越小越好,這樣它的下載和啟動速度都很快,一般我們都會選擇一個瘦了身的操作系統(例如 AlpineBusybox 等)作為基礎鏡像。

問題就在這裏,這些基礎鏡像的 init 系統也被抹掉了,這就是問題的根源!

init 系統有以下幾個特點:

  • 它是系統的第一個進程,負責產生其他所有用戶進程。
  • init 以守護進程方式存在,是所有其他進程的祖先。
  • 它主要負責:
    • 啟動守護進程
    • 回收孤兒進程
    • 將操作系統信號轉發給子進程

1. Docker 容器停止過程

對於容器來說,init 系統不是必須的,當你通過命令 docker stop mycontainer 來停止容器時,docker CLI 會將 TERM 信號發送給 mycontainer 的 PID 為 1 的進程。

  • 如果 PID 1 是 init 進程 – 那麼 PID 1 會將 TERM 信號轉發給子進程,然後子進程開始關閉,最後容器終止。
  • 如果沒有 init 進程 – 那麼容器中的應用進程(Dockerfile 中的 ENTRYPOINTCMD 指定的應用)就是 PID 1,應用進程直接負責響應 TERM 信號。這時又分為兩種情況:
    • 應用不處理 SIGTERM – 如果應用沒有監聽 SIGTERM 信號,或者應用中沒有實現處理 SIGTERM 信號的邏輯,應用就不會停止,容器也不會終止。
    • 容器停止時間很長 – 運行命令 docker stop mycontainer 之後,Docker 會等待 10s,如果 10s 后容器還沒有終止,Docker 就會繞過容器應用直接向內核發送 SIGKILL,內核會強行殺死應用,從而終止容器。

2. 容器進程收不到 SIGTERM 信號?

如果容器中的進程沒有收到 SIGTERM 信號,很有可能是因為應用進程不是 PID 1,PID 1 是 shell,而應用進程只是 shell 的子進程。而 shell 不具備 init 系統的功能,也就不會將操作系統的信號轉發到子進程上,這也是容器中的應用沒有收到 SIGTERM 信號的常見原因。

問題的根源就來自 Dockerfile,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT ./popcorn.sh

ENTRYPOINT 指令使用的是 shell 模式,這樣 Docker 就會把應用放到 shell 中運行,因此 shell 是 PID 1。

解決方案有以下幾種:

方案 1:使用 exec 模式的 ENTRYPOINT 指令

與其使用 shell 模式,不如使用 exec 模式,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT ["./popcorn.sh"]

這樣 PID 1 就是 ./popcorn.sh,它將負責響應所有發送到容器的信號,至於 ./popcorn.sh 是否真的能捕捉到系統信號,那是另一回事。

舉個例子,假設使用上面的 Dockerfile 來構建鏡像,popcorn.sh 腳本每過一秒打印一次日期:

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

#!/bin/sh

while true
do
    date
    sleep 1
done

構建鏡像並創建容器:

 → docker build -t truek8s/popcorn .
 → docker run -it --name corny --rm truek8s/popcorn

打開另外一個終端執行停止容器的命令,並計時:

 → time docker stop corny

因為 popcorn.sh 並沒有實現捕獲和處理 SIGTERM 信號的邏輯,所以需要 10s 左右才能停止容器。要想解決這個問題,就要往腳本中添加信號處理代碼,讓它捕獲到 SIGTERM 信號時就終止進程:

#!/bin/sh

# catch the TERM signal and then exit
trap "exit" TERM

while true
do
    date
    sleep 1
done

注意:下面這條指令與 shell 模式的 ENTRYPOINT 指令是等效的:

ENTRYPOINT ["/bin/sh", "./popcorn.sh"]

方案 2:直接使用 exec 命令

如果你就想使用 shell 模式的 ENTRYPOINT 指令,也不是不可以,只需將啟動命令追加到 exec 後面即可,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT exec ./popcorn.sh

這樣 exec 就會將 shell 進程替換為 ./popcorn.sh 進程,PID 1 仍然是 ./popcorn.sh

方案 3:使用 init 系統

如果容器中的應用默認無法處理 SIGTERM 信號,又不能修改代碼,這時候方案 1 和 2 都行不通了,只能在容器中添加一個 init 系統。init 系統有很多種,這裏推薦使用 tini,它是專用於容器的輕量級 init 系統,使用方法也很簡單:

  1. 安裝 tini
  2. tini 設為容器的默認應用
  3. popcorn.sh 作為 tini 的參數

具體的 Dockerfile 如下:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "./popcorn.sh"]

現在 tini 就是 PID 1,它會將收到的系統信號轉發給子進程 popcorn.sh

如果你想直接通過 docker 命令來運行容器,可以直接通過參數 --init 來使用 tini,不需要在鏡像中安裝 tini。如果是 Kubernetes 就不行了,還得老老實實安裝 tini。

3. 使用 tini 后應用還需要處理 SIGTERM 嗎?

最後一個問題:如果移除 popcorn.sh 中對 SIGTERM 信號的處理邏輯,容器會在我們執行停止命令后立即終止嗎?

答案是肯定的。在 Linux 系統中,PID 1 和其他進程不太一樣,準確地說應該是 init 進程和其他進程不一樣,它不會執行與接收到的信號相關的默認動作,必須在代碼中明確實現捕獲處理 SIGTERM 信號的邏輯,方案 1 和 2 乾的就是這個事。

普通進程就簡單多了,只要它收到系統信號,就會執行與該信號相關的默認動作,不需要在代碼中显示實現邏輯,因此可以優雅終止。

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

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

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

分享我入職阿里巴巴一百天的所思所悟_台中搬家

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

作者:薛勤,互聯網從業者,編程愛好者。 本文首發自公眾號:代碼藝術(ID:onblog)未經許可,不可轉載

非常高興的告訴大家,我已經入職阿里巴巴一百天了。一百天,是一個非常有紀念性的日子,也許你已經忘記你的百天照咋拍的,但是你的女盆友一定會提醒你別忘了百天紀念日給她買禮物。

加入阿里一百天了,這段時間我做了啥呢?

其實說來,加入阿里一百天我就完成了一件事,那就是自己負責的項目經過開發、測試於4月3日正式上線了,當我寫下通告全組的上線郵件時,心情還是不免有些激動的!

剛加入阿里的前三周吧,我基本上是全身心投入到學習中,這個學習不是學習什麼書本知識,而是去學習我所在小組負責的項目和業務。然後,去參加答辯,讓同事們去檢驗你的學習成果,順便考察你的學習能力和業務理解能力。

有的同學看到這裏,可能會嗤之以鼻,覺得只有研究技術才是最高大上的,曾經,我也是這樣想的。但是,真的讓你去研究開發什麼技術,你用什麼去研究、去開發呢?你真的了解技術背後的設計原理與業務場景嗎?

技術是為業務服務的,相信大家對這句話也有些了解。只有當業務遇到發展瓶頸時,技術才能體現出它的價值。

舉個例子,為什麼會有消息中間件呢?讓你去做消息中間價的研發工作,你怎麼去做?你第一步不得是去找業務溝通實際需求場景嗎?

有需求才有供應。不然,憑空想象嘛?

理解需求是研發技術的第一步,你還需要具備非常深厚的專業知識和研發經驗。不然,你造出來的輪子千瘡百孔,你覺得會有人用嗎?而且,你自己恐怕也意識不到你的輪子究竟有哪些孔洞需要去補,沒有一定的積累,造輪子除了爽之外沒有多少技術提升。

你有沒有發現,造輪子就是一種知識變現。很多人光着急着去變現了,而忘記去積累。這些人,我送他們一句話:先沸騰、再折騰。

想去造火箭是好事,說明你有上進心,但是,首先你得擰好螺絲。

當你離開校園,走進更高層次的圈子,經過不同思想火花的碰撞,你就會發現你很多堅持的、認為的,是錯的。

我之前寫過很多所謂的技術產品,比如分佈式限流、Web服務器等等,現在看來,都是玩具,用來樂呵樂呵虎虎人不錯。

回到正題,在阿里僥倖過了答辯之後,我成功接手了一個項目的開發工作。師兄指定我自己去做,不懂得再去問。

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

在師兄熱情的指導下,我順利理解了需求文檔,然後在數據庫新建表結構,接下來就是開發。

到這一步,我已經懂了要做什麼,但是怎麼去做,兩眼一抹黑。

公司項目不同於個人項目,最大的特點就是有自己的規範,用什麼記錄日誌,用什麼項目結構,用什麼必備的框架,都得安排上。

並不是直接用你的 IDE 創建個 Spring Boot 項目就完了,要是有這麼簡單那還是阿里嗎?這裡有一個小竅門,就是我們在課堂學習時的關鍵一步,看例題。

師兄給了我一句非常不錯的建議,先搭建系統的框架,寫一些接口,編寫整體邏輯,後面再慢慢去寫細節。事實證明,這個開發手段相當高明。

我寫代碼有個習慣,對一段自己沒有把握的代碼,先去寫單元測試,後來才知道這種方法很火,英文名叫 TDD,中文名叫測試驅動開發,是敏捷開發中的一項核心技術。

經過兩周時間,項目初步開發完成,我自己先對系統的一些接口進行模擬測試(自測),然後去打通我的上游,與上游一起進行流程測試(聯調),主流程沒有問題后,提交測試單(提測),測試人員會與我進行交接,開個會,拉個群。到了第二天,你會發現郵件爆滿,全是測試人員提交的缺陷單。

在這段溝通中,你須得有強大的心理素質和抗壓能力,以及較高的情商和交際能力,不然,頻繁的報錯會讓你懷疑自己的能力,心理素質不強者,甚至自暴自棄,覺得自己不適合這個行業。

很榮幸,我的缺陷單突破兩位數,可以說是慘不忍睹,原因就是沒有進行充分的自測,太過於依賴測試人員,實際上,這個想法完完全全就是錯的。這也是我第一次完成項目最大的經驗積累。

就在離項目上線還有一天的時間,突然爆出來個評估錯誤,需要對某一模塊進行重構。要知道,發現這個問題的存在已經耗費了很長時間。當意識到原來的設計方案已經走不通了,師兄立馬緊急研究新的方案,從開始研究到最後確定,只用了幾分鐘而已。

方案確定了,資源也正在申請,開發當然就由我來完成了。由於對新方案所涉及的技術一點不懂,但是明天一定要做完,只能晚上加班閱讀相關文檔了。

我先是去官網查看該技術的官方文檔,心裏有數之後再去 Github 看它的代碼,這裏不建議大家在 Github 網站在線查看,還是推薦 clone 到本地,看看 demo 就差不多搞明白了。第二天,如期完成開發和測試。

項目上線后,我心中依舊還是留有許多遺憾,比如一些功能由於後面的需求越來越多導致日誌記錄雜亂。

沒工作前我特別討厭說程序員就是只會拷貝和粘貼,沒想到自己也這麼幹了,其實我覺得拷貝沒有關係,有關係的是對拷貝的代碼壓根心裏沒數,當你對這段代碼進行修修補補時就很容易出問題。也許你已經懂了這段代碼的含義,但是拷貝的方案就是最好的方案嗎?這次我比較遺憾的就是沒有去重構我拷貝的這段代碼。

一百天的分享就告一段落,如果覺得文章不錯,可以訂閱我的個人公眾號“代碼藝術”(ID:onblog),你會收到我所創作的最新推送!

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

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司