Spring Security 實戰乾貨:如何實現不同的接口不同的安全策略

1. 前言

歡迎閱讀 Spring Security 實戰乾貨 系列文章 。最近有開發小夥伴提了一個有趣的問題。他正在做一個項目,涉及兩種風格,一種是給小程序出接口,安全上使用無狀態的JWT Token;另一種是管理後台使用的是Freemarker,也就是前後端不分離的Session機制。用Spring Security該怎麼辦?

2. 解決方案

我們可以通過多次繼承WebSecurityConfigurerAdapter構建多個HttpSecurityHttpSecurity 對象會告訴我們如何驗證用戶的身份,如何進行訪問控制,採取的何種策略等等。

如果你看過之前的教程,我們是這麼配置的:

/**
 * 單策略配置
 *
 * @author felord.cn
 * @see org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration
 * @since 14 :58 2019/10/15
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
@EnableWebSecurity
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class CustomSpringBootWebSecurityConfiguration {
    
    /**
     * The type Default configurer adapter.
     */
    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            super.configure(auth);
        }

        @Override
        public void configure(WebSecurity web) {
            super.configure(web);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 配置 httpSecurity

        }
    }
}

上面的配置了一個HttpSecurity,我們如法炮製再增加一個WebSecurityConfigurerAdapter的子類來配置另一個HttpSecurity。伴隨而來的還有不少的問題要解決。

2.1 如何路由不同的安全配置

我們配置了兩個HttpSecurity之後,程序如何讓小程序接口和後台接口走對應的HttpSecurity

HttpSecurity.antMatcher(String antPattern)可以提供過濾機制。比如我們配置:

     @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 配置 httpSecurity
            http.antMatcher("/admin/v1");

        }

那麼該HttpSecurity將只提供給以/admin/v1開頭的所有URL。這要求我們針對不同的客戶端指定統一的URL前綴。

舉一反三隻要HttpSecurity提供的功能都可以進行個性化定製。比如登錄方式,角色體系等。

2.2 如何指定默認的HttpSecurity

我們可以通過在WebSecurityConfigurerAdapter實現上使用@Order註解來指定優先級,數值越大優先級越低,沒有@Order註解將優先級最低。

2.3 如何配置不同的UserDetailsService

很多情況下我們希望普通用戶和管理用戶完全隔離,我們就需要多個UserDetailsService,你可以在下面的方法中對AuthenticationManagerBuilder進行具體的設置來配置UserDetailsService,同時也可以配置不同的密碼策略。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setUserDetailsService(new UserDetailsService() {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 自行實現
            return  null ;
        }
    });
    // 也可以設計特定的密碼策略
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder);
    auth.authenticationProvider(daoAuthenticationProvider);
}

2.4 最終的配置模板

上面的幾個問題解決之後,我們基本上掌握了在一個應用中執行多種安全策略。配置模板如下:

/**
 * 多個策略配置
 *
 * @author felord.cn
 * @see org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration
 * @since 14 :58 2019/10/15
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
@EnableWebSecurity
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class CustomSpringBootWebSecurityConfiguration {

    /**
     * 後台接口安全策略. 默認配置
     */
    @Configuration
    @Order(1)
    static class AdminConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            //用戶詳情服務個性化
            daoAuthenticationProvider.setUserDetailsService(new UserDetailsService() {
                @Override
                public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                    // 自行實現
                    return null;
                }
            });
            // 也可以設計特定的密碼策略
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder);
            auth.authenticationProvider(daoAuthenticationProvider);
        }

        @Override
        public void configure(WebSecurity web) {
            super.configure(web);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 根據需求自行定製
            http.antMatcher("/admin/v1")
                    .sessionManagement(Customizer.withDefaults())
                    .formLogin(Customizer.withDefaults());


        }
    }

    /**
     * app接口安全策略. 沒有{@link Order}註解優先級比上面低
     */
    @Configuration
    static class AppConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            //用戶詳情服務個性化
            daoAuthenticationProvider.setUserDetailsService(new UserDetailsService() {
                @Override
                public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                    // 自行實現
                    return null;
                }
            });
            // 也可以設計特定的密碼策略
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder);
            auth.authenticationProvider(daoAuthenticationProvider);
        }

        @Override
        public void configure(WebSecurity web) {
            super.configure(web);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 根據需求自行定製
            http.antMatcher("/app/v1")
                    .sessionManagement(Customizer.withDefaults())
                    .formLogin(Customizer.withDefaults());


        }
    }
}

3. 總結

今天我們解決了如何針對不同類型接口採取不同的安全策略的方法,希望對你有用,如果你有什麼問題可以留言。多多關注:碼農小胖哥,更多乾貨奉上。

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

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

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

【Java Spring Cloud 實戰之路】添加一個SpringBootAdmin監控

0. 前言

在之前的幾章中,我們先搭建了一個項目骨架,又搭建了一個使用nacos的gateway網關項目,網關項目中並沒有配置太多的東西。現在我們就接着搭建在Spring Cloud 微服務中另一個重要的項目 – Spring boot admin.

1. Spring Boot Admin 介紹

Spring Boot Admin 用來監控基於Spring Boot的應用,在Spring Boot Actuator的基礎上提供了簡潔的可視化Web UI。Spring Boot Admin 提供了以下功能:

  • 显示應用的健康狀態
  • 显示應用的細節內容: JVM和內存信息,micrometer信息, 數據源信息,緩存信息等
  • 显示 編譯版本
  • 查看和下載日誌
  • 查看jvm參數和環境變量值
  • 查看Spring Boot項目配置
  • 显示 thread dump
  • 显示 http-traces

……

等一系列內容。

2. 創建一個 Spring Boot Admin項目

那麼,我們就來創建一個Spring Boot Admin 項目吧。

2.1 創建 Spring Boot Admin 服務端

在manager 目錄下,創建一個 monitor目錄,並在monitor目錄下創建一個pom.xml 文件,添加以下內容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>monitor</artifactId>
    <version>${revision}</version>
    <packaging>jar</packaging>
    <parent>
        <artifactId>manager</artifactId>
        <groupId>club.attachie</groupId>
        <version>${revision}</version>
    </parent>

</project>

在 manager/pom.xml 註冊我們新建的項目模塊:

<modules>
    <module>gateway</module>
    <module>monitor</module>
</modules>

在 monitor 創建如下目錄:

.
├── pom.xml
└── src
    └── main
        ├── java
        └── resources

在根目錄的pom.xml 添加 Spring Boot Admin 依賴:

先添加spring-boot-admin版本號變量:

<spring-boot-admin.version>2.2.3</spring-boot-admin.version>

並在dependencyManagement > dependencies 下添加:

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>${spring-boot-admin.version}</version>
</dependency>

在monitor/pom.xml文件中添加:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
    </dependency>
</dependencies>

運行

mvn clean install

檢查並刷mvn引用緩存。

創建MonitorApplication類:

package club.attachie.nature.monitor;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAdminServer
public class MonitorApplication {
    public static void main(String[] args) {
        SpringApplication.run(MonitorApplication.class, args);
    }
}

啟動后能看到如下界面:

3 與網關服務進行互通

在上一篇中,我們添加了Spring Cloud Gateway項目,到目前為止兩個項目之間完全割裂沒有關聯。在這一節,我們在兩者之間建立關聯。也就是說,將gateway 項目引入Spring Admin Boot監聽。

在 manager/gateway 的pom.xml 文件中加入如下引用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然後修改 gateway項目的啟動端口,在resources/bootstrap.yml 添加:

server:
  port: 8070

在 monitor中加入nacos引用:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>      
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改MonitorApplication 為:

package club.attachie.nature.monitor;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;

@SpringBootApplication
@EnableAdminServer
@RefreshScope
public class MonitorApplication {
    public static void main(String[] args) {
        SpringApplication.run(MonitorApplication.class, args);
    }
}

創建monitor項目的bootsrap.yml:

spring:
  application:
    name: monitor
  
  cloud:
  	nacos:
      discovery:
        server-addr: 127.0.0.1:8848

關於這裏的配置 在上一篇 中有個錯誤,應該是 discovery > server-addr,不是 config > server-addr。兩者有區別,discovery表示設置nacos為服務發現中心,config表示nacos為配置中心。

啟動 gateway 項目和 monitor項目查看效果, 訪問 8080端口:

可以看到兩個應用可以被發現,如果沒有設置monitor項目把nacos當做服務發現中心,將無法獲取到具體在線的應用。點擊 gateway 進去后可以查看到:

4. 總結

我們搭建了一個Spring Boot Admin 項目作為一個監控系統,後續會在這裏添加更多的內容。

更多內容煩請關注我的博客《高先生小屋》

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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

通過與C++程序對比,徹底搞清楚JAVA的對象拷貝

目錄

  • 一、背景
  • 二、JAVA對象拷貝的實現
    • 2.1 淺拷貝
    • 2.2 深拷貝的實現方法一
    • 2.3 深拷貝的實現方法二
      • 2.3.1 C++拷貝構造函數
      • 2.3.2 C++源碼
      • 2.3.3 JAVA通過拷貝構造方法實現深拷貝
  • 四、總結

一、背景

JAVA編程中的對象一般都是通過new進行創建的,新創建的對象通常是初始化的狀態,但當這個對象某些屬性產生變更,且要求用一個對象副本來保存當前對象的“狀態”,這時候就需要用到對象拷貝的功能,以便封裝對象之間的快速克隆。

二、JAVA對象拷貝的實現

2.1 淺拷貝
  • 被複制的類需要實現Clonenable接口;
  • 覆蓋clone()方法,調用super.clone()方法得到需要的複製對象;
  • 淺拷貝對基本類型(boolean,char,byte,short,float,double.long)能完成自身的複製,但對於引用類型只對引用地址進行拷貝。
    — 下面我們用一個實例進行驗證:
/**
 * 單隻牌
 *
 * @author zhuhuix
 * @date 2020-06-10
 */
public class Card implements Comparable, Serializable,Cloneable {

    // 花色
    private String color = "";
    //数字
    private String number = "";

    public Card() {
    }

    public Card(String color, String number) {
        this.color = color;
        this.number = number;
    }

    public String getColor() {
        return this.color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getNumber() {
        return this.number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return this.color + this.number;
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof Card) {
            int thisColorIndex = Constant.COLORS.indexOf(this.getColor());
            int anotherColorIndex = Constant.COLORS.indexOf(((Card) o).getColor());
            int thisNumberIndex = Constant.NUMBERS.indexOf(this.getNumber());
            int anotherNumberIndex = Constant.NUMBERS.indexOf(((Card) o).getNumber());

            // 大小王之間相互比較: 大王大於小王
            if ("JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                    return thisColorIndex > anotherColorIndex ? 1 : -1;
            }

            // 大小王與数字牌之間相互比較:大小王大於数字牌
            if ("JOKER".equals(this.color) && !"JOKER".equals(((Card) o).getColor())) {
                return 1;
            }
            if (!"JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                return -1;
            }

            // 数字牌之間相互比較: 数字不相等,数字大則牌面大;数字相等 ,花色大則牌面大
            if (thisNumberIndex == anotherNumberIndex) {
                return thisColorIndex > anotherColorIndex ? 1 : -1;
            } else {
                return thisNumberIndex > anotherNumberIndex ? 1 : -1;
            }

        } else {
            return -1;
        }
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
 * 撲克牌常量定義
 *
 * @author zhuhuix
 * @date 2020-06-10
 */
public class Constant {

    // 紙牌花色:黑桃,紅心,梅花,方塊
    final static List<String> COLORS = new ArrayList<>(
            Arrays.asList(new String[]{"", "", "", ""}));
    // 紙牌数字
    final static List<String> NUMBERS = new ArrayList<>(
            Arrays.asList(new String[]{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"}));
    // 大王小王
    final static List<String> JOKER = new ArrayList<>(
            Arrays.asList(new String[]{"小王","大王"}));
}

/**
 * 整副副撲克牌
 *
 * @author zhuhuix
 * @date 2020-06-10
 */
public class Poker implements Cloneable, Serializable {

    private List<Card> cards;

    public Poker() {
        List<Card> cardList = new ArrayList<>();
        // 按花色與数字組合生成52張撲克牌
        for (int i = 0; i < Constant.COLORS.size(); i++) {
            for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
            }
        }
        // 生成大小王
        for (int i = 0; i < Constant.JOKER.size(); i++) {
            cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
        }

        this.cards = cardList;
    }

   
    // 從整副撲克牌中抽走大小王
    public void removeJoker() {
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            Card cardJoker = iterator.next();
            if (cardJoker.getColor() == "JOKER") {
                iterator.remove();
            }
        }
    }

    public List<Card> getCards() {
        return cards;
    }

    public void setCards(List<Card> cards) {
        this.cards = cards;
    }

    public Integer getCardCount() {
        return this.cards.size();
    }

    @Override
    public String toString() {
        StringBuilder poker = new StringBuilder("[");
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            poker.append(iterator.next().toString() + ",");
        }
        poker.setCharAt(poker.length() - 1, ']');
        return poker.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

/**
 * 測試程序
 *
 * @author zhuhuix
 * @date 2020-6-10
 */
public class PlayDemo {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 生成一副撲克牌並洗好牌
        Poker poker1 = new Poker();
        System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 張:"+poker1.toString());

        Poker poker2= (Poker) poker1.clone();
        System.out.println("第一副牌拷頁生成第二副牌,共 "+poker2.getCardCount()+" 張:"+poker2.toString());

        poker1.removeJoker();

        System.out.println("====第一副牌抽走大小王后====");
        System.out.println("第一副牌還有 "+poker1.getCardCount()+" 張:"+poker1.toString());
        System.out.println("第二副牌還有 "+poker2.getCardCount()+" 張:"+poker2.toString());

    }

}
  • 運行結果:
    在第一副的對象中抽走了“大小王”,克隆的第二副的對象的“大小王”竟然也被“抽走了”
2.2 深拷貝的實現方法一
  • 被複制的類需要實現Clonenable接口;
  • 覆蓋clone()方法,自主實現引用類型成員的拷貝複製。
    — 我們只要改寫一下Poker類中的clone方法,讓引用類型成員實現複製:
/**
 * 整副副撲克牌--自主實現引用變量的複製
 *
 * @author zhuhuix
 * @date 2020-06-10
 */
public class Poker implements Cloneable, Serializable {

    private List<Card> cards;

    public Poker() {
        List<Card> cardList = new ArrayList<>();
        // 按花色與数字組合生成52張撲克牌
        for (int i = 0; i < Constant.COLORS.size(); i++) {
            for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
            }
        }
        // 生成大小王
        for (int i = 0; i < Constant.JOKER.size(); i++) {
            cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
        }

        this.cards = cardList;
    }

    // 從整副撲克牌中抽走大小王
    public void removeJoker() {
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            Card cardJoker = iterator.next();
            if (cardJoker.getColor() == "JOKER") {
                iterator.remove();
            }
        }
    }

    public List<Card> getCards() {
        return cards;
    }

    public void setCards(List<Card> cards) {
        this.cards = cards;
    }

    public Integer getCardCount() {
        return this.cards.size();
    }

    @Override
    public String toString() {
        StringBuilder poker = new StringBuilder("[");
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            poker.append(iterator.next().toString() + ",");
        }
        poker.setCharAt(poker.length() - 1, ']');
        return poker.toString();
    }

	// 遍歷原始對象的集合,對生成的對象進行集合複製
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Poker newPoker = (Poker)super.clone();
        newPoker.cards = new ArrayList<>();
        newPoker.cards.addAll(this.cards);
        return newPoker;
    }
}

  • 輸出結果:
    — 通過自主實現引用類型的複製,原對象與對象的拷貝的引用類型成員地址不再關聯
2.3 深拷貝的實現方法二
  • 在用第二種方式實現JAVA深拷貝之前,我們首先對C++程序的對象拷貝做個了解:
2.3.1 C++拷貝構造函數

C++拷貝構造函數,它只有一個參數,參數類型是本類的引用,且一般用const修飾

2.3.2 C++源碼
// 單隻牌的類定義
// Created by Administrator on 2020-06-10.
//

#ifndef _CARD_H
#define _CARD_H

#include <string>

using namespace std;

class Card {
private :
    string color;
    string number;
public:
    Card();

    Card(const string &color, const string &number);

    const string &getColor() const;

    void setColor(const string &color);

    const string &getNumber() const;

    void setNumber(const string &number);

    string toString();

};


#endif //_CARD_H

// 單隻牌類的實現
// Created by Administrator on 2020-06-10.
//

#include "card.h"

Card::Card(){}

Card::Card(const string &color, const string &number) : color(color), number(number) {}

const string &Card::getColor() const {
    return color;
}

void Card::setColor(const string &color) {
    Card::color = color;
}

const string &Card::getNumber() const {
    return number;
}

void Card::setNumber(const string &number) {
    Card::number = number;
}


string Card::toString() {
    return getColor()+getNumber();
}




// 撲克牌類的定義
// Created by Administrator on 2020-06-10.
//

#ifndef _POKER_H
#define _POKER_H

#include <vector>
#include "card.h"

using namespace std;

const int COLOR_COUNT=4;
const int NUMBER_COUNT=13;
const int JOKER_COUNT=2;

const string COLORS[COLOR_COUNT] = {"", "", "", ""};
const string NUMBERS[NUMBER_COUNT]={"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
const string JOKER[JOKER_COUNT] ={"小王","大王"};

class Poker {
private:
    vector<Card> cards;
public:
    Poker();

    Poker(const Poker &poker);

    const vector<Card> &getCards() const;

    void setCards(const vector<Card> &cards);

    int getCardCount();

    void toString();

    void clear();
};


#endif //_POKER_H

// 撲克牌類的實現
// Created by zhuhuix on 2020-06-10.
//

#include "Poker.h"
#include <iostream>

const vector<Card> &Poker::getCards() const {
    return this->cards;
}

void Poker::setCards(const vector<Card> &cards) {
    Poker::cards = cards;
}

// 構造函數
Poker::Poker() {
    for (int i = 0; i < NUMBER_COUNT; i++) {
        for (int j = 0; j < COLOR_COUNT; j++) {
            this->cards.emplace_back(COLORS[j], NUMBERS[i]);
        }
    }
    for (int i = 0; i < JOKER_COUNT; i++) {
        this->cards.emplace_back("JOKER", JOKER[i]);
    }
}

// 拷貝構造函數
Poker::Poker(const Poker &poker) {
    for (int i = 0; i < poker.getCards().size(); i++) {
        this->cards.emplace_back(poker.cards[i].getColor(), poker.cards[i].getNumber());
    }
}

int Poker::getCardCount() {
    return this->cards.size();
}

void Poker::toString() {
    cout << "共" << getCardCount() << "張牌:";
    cout << "[";
    for (int i = 0; i < this->cards.size(); i++) {
        cout << this->cards[i].toString();
        if (i != getCardCount() - 1) {
            cout << ",";
        }
    }
    cout << "]" << endl;

}

void Poker::clear() {
    this->cards.clear();
}

// 主測試程序
// Created by Administrator on 2020-06-10.
//

#include "Poker.h"
#include <iostream>

using namespace std;

int main() {
    Poker poker1;
    cout << "第一副牌:";
    poker1.toString();
    // 通過拷貝構造函數生成第二副牌
    Poker poker2(poker1);
    cout << "第二副牌:";
    poker2.toString();
    // 清除撲克牌1
    poker1.clear();
    cout << "清空后,第一副牌:";
    poker1.toString();
    cout << "第二副牌:";
    poker2.toString();
    return 0;
}
  • 輸出:
2.3.3 JAVA通過拷貝構造方法實現深拷貝
  • JAVA拷貝構造方法與C++的拷貝構造函數相同,被複制對象的類需要實現拷貝構造方法:
    首先需要聲明帶有和本類相同類型的參數構造方法
    其次拷貝構造方法可以通過序列化實現快速複製
  • 拷貝對象通過調用拷貝構造方法進行創建。
    — 我們再改寫一下Poker類,實現拷貝構造方法:
/**
 * 整副副撲克牌--實現拷貝構造方法
 *
 * @author zhuhuix
 * @date 2020-06-10
 */
public class Poker implements Serializable {

    private List<Card> cards;

    public Poker() {
        List<Card> cardList = new ArrayList<>();
        // 按花色與数字組合生成52張撲克牌
        for (int i = 0; i < Constant.COLORS.size(); i++) {
            for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
            }
        }
        // 生成大小王
        for (int i = 0; i < Constant.JOKER.size(); i++) {
            cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
        }

        this.cards = cardList;
    }

    // 拷貝構造方法:利用序列化實現深拷貝
    public Poker(Poker poker) {

        try {

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(os);
            oos.writeObject(poker);

            ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(is);
            this.cards = ((Poker) ois.readObject()).getCards();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    // 從整副撲克牌中抽走大小王
    public void removeJoker() {
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            Card cardJoker = iterator.next();
            if (cardJoker.getColor() == "JOKER") {
                iterator.remove();
            }
        }
    }

    public List<Card> getCards() {
        return cards;
    }

    public void setCards(List<Card> cards) {
        this.cards = cards;
    }

    public Integer getCardCount() {
        return this.cards.size();
    }

    @Override
    public String toString() {
        StringBuilder poker = new StringBuilder("[");
        Iterator<Card> iterator = this.cards.iterator();
        while (iterator.hasNext()) {
            poker.append(iterator.next().toString() + ",");
        }
        poker.setCharAt(poker.length() - 1, ']');
        return poker.toString();
    }
}

  • 對測試主程序進行修改:
/**
 * 測試程序
 *
 * @author zhuhuix
 * @date 2020-6-10
 */
public class PlayDemo {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 生成一副撲克牌並洗好牌
        Poker poker1 = new Poker();
        System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 張:"+poker1.toString());

        Poker poker2 = new Poker(poker1);
        System.out.println("第一副牌拷頁生成第二副牌,共 "+poker2.getCardCount()+" 張:"+poker2.toString());

        poker1.removeJoker();

        System.out.println("====第一副牌抽走大小王后====");
        System.out.println("第一副牌還有 "+poker1.getCardCount()+" 張:"+poker1.toString());
        System.out.println("第二副牌還有 "+poker2.getCardCount()+" 張:"+poker2.toString());


        Poker poker3 = new Poker(poker1);
        System.out.println("第三副牌還有 "+poker3.getCardCount()+" 張:"+poker3.toString());
    }

}
  • 輸出結果:
    –通過序列化的有手段,同樣也能實現對象的深拷貝

四、總結

  • java程序進行對象拷貝時,如果對象的類中存在引用類型時,需進行深拷貝
  • 對象拷貝可以通過實現Cloneable接口完成
  • java編程也可仿照 C++程序的拷貝構造函數,實現拷貝構造方法進行對象的複製
  • 通過序列化與反序化手段可實現對象的深拷貝

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

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

※回頭車貨運收費標準

本田摘得Google2016年度搜尋冠軍,繽智在其中扮演怎樣的角色?

繽智的內飾也凸顯着先鋒人群的獨到品味:充滿朝氣的撞色設計,搭配非對稱航空式座艙,這一設計巧思,頗受年輕人青睞,傳達出他們的脫俗格調。懸浮式儀錶台、多變魔術杯架等設計同樣極為出彩,縱享人性化舒適的同時,又展現了他們務實的另一面。

據Google Trends近日公布的2016年度搜尋清單來看,在汽車品牌這一類別中,位列榜首的並非德系三強,而是一向以技術著稱的日系本田。更值得一提的是,Google今年採用了與去年不同的計算方式,既結合了去年的搜尋次數,亦結合了“最高搜索流量和最持久峰值搜索流量”的數據,由此產生的榜單含金量相比往年來得更高。如此看來,在高含金量的2016榜單之中,日系本田力壓奔馳、特斯拉等一眾豪華德系美系品牌獲此殊榮,更具說服力。

SUV表現突出,本田關注節節攀升

那麼,本田奪冠的奧秘又在哪裡?這與本田SUV車型的賣座息息相關。SUV車型無論在國內抑或是全球市場來說均為大熱車型,其中更以價格親民、功能實用的小型SUV尤為突出,而本田旗下的小型SUV—— HR-V(國內繽智)則在全球範圍內掀起了“本田熱”。作為一款全球車型,HR-V在國內有着一個消費者更為熟悉的名字——繽智。繽智採用了全球化的概念設計,其身為SUV的多面實用性、類Coupe的時尚外觀、MpV化的空間配置,都極為貼切的迎合了國內乃至國外車主的需求。

憑藉全球車型的特殊身份,繽智在國內市場早已擁有較高人氣。以即將過去的2016年為例,廣汽本田繽智穩坐1至11月熱門合資車型銷量桂冠寶座,其中以11月銷量為例,繽智當月終端銷量高達17,485輛,同比增長45.1%,成績喜人的同時又不免讓人深思:繽智憑什麼打動眾多消費者的心?

先鋒產品力 打造引領潮流的時尚座駕

面對來自年輕化的受眾群體與瞬息萬變的時尚風向的雙重壓力,單純追逐潮流的設計已顯得有些捉襟見肘,在更多時候,只有引領潮流才能受人追捧持久不衰,而繽智就深知此道。在這個“看顏”的時代,繽智外觀具有天生優勢,更引領着年輕時尚人群對車型外觀的美學要求。

那麼,繽智的獨特優勢又體現在何處呢?外觀整體以鑽石切面的幾何視覺效果為理念,頗為符合當下主流時尚圈審美;稜角分明的前臉與LED投影式前大燈組的組合極具時尚美學;側身上揚的腰線和隱藏式後門把手設計則頗有幾分Coupe風味,符合時下國際車型設計風向及國際化大眾口味。

繽智的內飾也凸顯着先鋒人群的獨到品味:充滿朝氣的撞色設計,搭配非對稱航空式座艙,這一設計巧思,頗受年輕人青睞,傳達出他們的脫俗格調;懸浮式儀錶台、多變魔術杯架等設計同樣極為出彩,縱享人性化舒適的同時,又展現了他們務實的另一面。

同時,繽智作為以黑科技著稱的廣本首款SUV車型,智能配置自然不容小覷,輕鬆滿足受眾需求。SmartEntry智能無匙進入系統輕鬆拉開車門坐進車內,按下一鍵啟動功能,懸浮式儀錶盤隨即點亮,盡顯科技感更充分調動了駕馭的熱情。與此同時,兼具手機屏幕映射功能的智能屏互聯繫統也十分便捷。

追本溯源,無論一款車其他表現如何,與受眾匹配的動力系統都必須具備,而繽智要迎合的是對速度與駕馭快感追求都較高的年輕時尚人群,要求不可謂不高。繽智用銷量證明了自身的優異,以1.8L i-VTEC發動機打入市場,配合CVT無級變速器與AWD四驅系統,不但滿足了先鋒人士速度的追求又兼顧了良好的通過性。而之後上市的1.5L車型則搭載了地球夢科技發動機,不只滿足了年輕人不同的動力需求,官方給出的6.8L/100km的綜合油耗也滿足了年輕群體經濟環保願望,並由此受到了熱捧。

廣本里程碑 繽智後市值得期待

作為廣汽本田首款SUV車型,繽智自上市起便肩負廣汽本田打入國內SUV市場的重任。在上市短短2年時間里,繽智就以累計快高達30萬用戶,一舉成為廣汽本田旗下主打車型,同時更奪得前十一個月合資品牌小型SUV銷量冠軍。在幫助本田力壓其他日系對手成就日系在華銷量第一的同時,繽智早已不負眾望地扛起了廣汽本田SUV市場的大梁。

在SUV市場如日中天的今天,繽智作為小型SUV的旗幟車型,憑藉潮流先鋒的外觀、品味獨到的內飾、科技感十足的配置與洶湧澎湃的動力,不只滿足了年輕人的多元化購車需求,更肩擔起了引領市場潮流風向的重任,如此看來,繽智獲得銷量冠軍是綜合實力的體現,帶動本田博得Google年度熱搜。

再看此次Google榜單,奔馳、特斯拉位列二三,相比旗下GLC與Model 3等熱門車型,像國外的HR-V抑或是國內的繽智這樣的小型車型來說,更為親民的價格,同樣豐富的空間,以及不錯的通過性和全面的配置表現,都是其更為大熱的理由。今後也會有越來越多的年輕消費者會選擇經濟性更高的小型SUV,繽智作為熱門小型SUV,熱銷勢將繼續升溫。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

7.98萬起產品力不輸哈弗H6,這款SUV車主是怎樣評價的?

最不滿意的地方:起步有點肉,而且這個時候發動機聲音是比較大的。所以感覺上這個發動機實際動力還是比較一般的,而且發動機倉隔音也是比較一般。車主:老兵哥購買車型:北汽幻速S6 2017款 1。5T CVT樂享型裸車購買價:10。

前言

作為國內的造車大戶,北汽奉行着“多生孩兒多掙錢”的政策,有注重一般家用市場的北汽紳寶、有注重硬派SUV市場的北京汽車、還有着北汽幻速以及北汽比速這兩個入門品牌。那麼作為一個比較年輕的品牌,北汽幻速的口碑究竟怎樣呢?今天筆者就搜集了幾位北汽幻速S6車主的意見,這款7.98萬起售的緊湊型SUV有着該價位中不俗的競爭力。全系標配的是1.5T發動機,而且有着CVT變速箱作為自動擋的選擇。

那麼多孩子,打起群架肯定贏

北汽銀翔幻速S6

官方指導價:7.98-11.68萬

編者意見:

性價比較高,動力表現在同價位中表現比較優秀。不過全系沒能標配ESp車身穩定系統以及电子助力轉向比較可惜。

車主:BY2000

購買車型:北汽幻速S6 2017款 1.5T CVT尊享型

裸車購買價11.68 萬元

最滿意的地方:整體都比較滿意,但是最滿意的的是價格,性價比很高。能在這個價格買到這樣配置的緊湊型SUV還是不錯的,而且是渦輪增壓發動機。

最不滿意的地方:起步有點肉,而且這個時候發動機聲音是比較大的。所以感覺上這個發動機實際動力還是比較一般的,而且發動機倉隔音也是比較一般。

車主:老兵哥

購買車型:北汽幻速S6 2017款 1.5T CVT樂享型

裸車購買價:10.68 萬元

最滿意的地方:乘坐空間,沒有想到這個價格的車還能有着那麼大的空間,滿載的情況下也不是很擁擠。而且後備箱容積也是相當可觀,大天窗還有那麼多的配置,買這款車真的是比較值。

最不滿意的地方:裝配工藝有待加強,有些部分的縫隙是比較大的,就如尾門的縫隙,看着很掉價,而且方向盤塑料感太強了。

車主:smg20900

購買車型:北汽幻速S6 2017款 1.5T CVT尊享型

裸車購買價:10.68 萬元

最滿意的地方:外觀,看着更像是二十多萬的SUV。能給人更多的面子,而且在動力方面感覺還是不錯的,一直都可以維持在較低轉速,120km/h時速下轉速也只是2200rpm左右,這個是最為滿意的,所以綜合油耗上也是9L左右,對於一款SUV來說是滿意了。

最不滿意的地方:儲物空間實在是少得可憐,中間只有一個杯架,不夠用。其次是噪音的問題,不過對於如此便宜的車來說,還是可以接受的。

編者總結:

北汽雖然是個歷史悠長的品牌,但事實基本是為別人“代工”,自身在工藝方面以及控製成本方面還是需要向合資學習,所以在做工以及隔音用料上表現一般。不過在主要的發動機上表現卻是相當不錯,雖然渦輪遲滯現象還是有的,但是油耗表現卻是令人信服的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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

比埃爾法還大的MPV! 20萬就能買你敢信?

0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。

早些年的時候看到MpV總覺得和麵包車差不多,但是現在的MPV越來越豪華,空間上又有轎車無可比擬的優勢,逐漸成為很多公司和家庭的多用途車首選。

今天我們就為大家帶來福特途睿歐與大眾邁特威的對比評測。

空間

既然是MpV,那麼空間肯定是很重要的部分,你說轎車有多長軸距,在MpV面前全部都是渣渣 。此次試駕的兩台車均為7座車型。坐姿足夠舒服,坐墊支撐到位,腿部空間?自己看!

再到後面,福特途睿歐的第三排座椅擁有三個可獨立調整的靠背,而邁特威第三排的中間座椅就稍微吃虧了,但是可以抽出一個扶手,兩個人乘坐的時候比較舒適。

不過邁特威最爽的用法應該是這樣。

儲物空間

在以往轎車的評測中,我們會到處找放水杯的地方,但是在MPV上真的就是多擔心了,兩台車的空間都是可以用海量來形容,圖中皆為2L大水瓶!!!

福特途睿歐的杯架

邁特威的杯架

除此之外邁特威座椅下方空間還有神奇的儲物空間。途睿歐取而代之的則是獨立的空調出風口。

一般7座SUV如果使用第三排座椅時,後備箱基本上沒有什麼空間,來到MpV上。情況則有所不同。如有需要福特途睿歐還可以將第三排座椅翻起,除了可以開去買菜,心情好還可以開去賣菜。

邁特威的後備箱空間也不俗,不過座椅只能向前移,不能繼續將座椅翻起來。

動力

雖然一台MpV你不指望它能開多快,但是動力就像存款一樣,可以不用,但不能沒有。途睿歐搭載的是福特ECOboost的2.0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。

雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。

而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。加上DQ500的7速雙離合變速箱,動力是足,但是感覺變速箱是刻意放慢了動作以尋求一個平順的效果,大腳油門並不會直接降檔,而是會選擇拉高發動機轉速,並且傳遞到車廂的發動機聲音較大。

配置對比

福特途睿歐配上了這個級別少有的後排天窗。

邁特威則是側滑的玻璃窗。

邁特威兩側都用上了電動門,這一點福特途睿歐上略有遺憾。

舒適性對於MpV同樣重要,福特途睿歐用上了空氣懸挂,這也是福特專門為中國研發的,對於細碎過濾相當到位,國內的這套空氣懸挂是標配,而在英國也只有改款后的頂配車型才配備。

邁特威則是自家的DCC動態懸挂,兩種懸挂相比各有特色。不過有一個細節方面則是搞不懂大眾,這個最常用的扶手,調節起高低真的有點反人類。

總結

邁特威和福特途睿歐相比各有優勢,但是從價格看來,一台邁特威的價格能夠買兩台福特途睿歐了,邁特威(41.88-54.98萬);福特途睿歐(17.69-20.39萬)。質感方面邁特威會更好一些,但是福特途睿歐在如此價位還能取得如此不俗的成績實屬驚人。大家會怎麼選呢?

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

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

※回頭車貨運收費標準

Vue —— 精講 VueX (1)

大綱

這一講我們最主要的就是學習vue中的數據管理VueX,這個是一個大殺器

demo源代碼地址 https://github.com/BM-laoli/BMlaoli-learn-VueX

一、回顧一些Promise相關的東西

Promise 有幾個比較重要的方法,最重要的還是有一個叫做all的方法,這個也是非常的強大的

假設我們目前要求,希望能按順序的拿到先後的兩個ajax那麼我應該怎麼處理呢

Promse.all( [
new Promose( ( resolve,rejcet ) => {
        $.ajax({
            url:'xxxx',
            data:'xxxx',
            sucess:(res) => {
                resolve(res)
            }
        })
        $.ajax({
            url:'xxxx',
            data:'xxxx',
            sucess:(res) => {
                resolve(res)
            }
        })
    })

]).then( results => {
    consel.log(results)
    // 這樣拿到的就是一個數組了, 先後的順序就是裏面的值
} )

注意啊這裏對promise的深入的解釋說明

  1. 首先我們的兩個回調resolve 還有reject注意啊,
這兩個回調回調函數是 在傳入的時候定義的,但是調用是在promse里調的!這兩個參數是函數!!函數!!回調函數!

一、概念

Vue官方介紹
絕大多數的管方都非常喜歡用概念來解釋概念,這就有點難搞了,我這個概念的都不懂,你又給我搞另一個概念
實際上那個Vuex就是一個大管家,統一進行管理,全局的單例模式

1.最通俗的解釋

Vuex實際上就是一個 用來放 一些組件共享的數據的,實際上這可能是是下面這些情況

  1. 登錄
    假設我們目前有50+頁面。我們都每一個頁面都要發送接口請求而且這些請求需要token,那麼如果我是登錄的,我就需要在每一個頁面拿到我的登錄token這樣就造成了數據的傳來傳去非常麻煩,如果我們有一個公共的地方來放這些東西就好了

  2. 購物車。收藏
    也會有這種組件之間打出傳值的情況發生,那麼我如何管理這些東西呢,這個就是一個問題

綜上所述,我們需要使用Vuex*

二、如何入門的使用

2.簡單的使用

這裏假設有這樣的一個需求:我們目前有兩個組件App.vue 還有BMlaoli.vue 我呢,他們之間有層級的關係,app裏面有一個變量叫做contuend 我希望我在app裏面對countend的操作能夠動態的傳遞到我們的BMlaoli里,而且不使用父子組件傳值,那麼我們如何做呢?親看下面講演

  1. 首先我們需要有兩個組件
    他們都是最基礎的樣子

App

<template>
  <div id="app">
    <h1> 我是vueapp </h1>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {
  }
}
</script>

<style>
</style>

BMlaoli


<template>
    <div>
        <h1>我是bm界面</h1>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style lang="sass" scoped>

</style>
  1. app的業務邏輯
<template>
  <div id="app">
    <p>{{contuned}}</p>
    
      <button @click="contuned ++" >+</button>
      <button @click="contuned --" >-</button>

  </div>
</template>

<script>

import bmlao from '@/components/Bmlaoli';

export default {
  name: 'App',
  components: {
    bmlao,
  },
  data() {
    return {
      contuned: 100
    }
  },
}
</script>

<style>
</style>

但是問題來了,我目前希望你們在app裏面做的更改可以反映到我的Bm組件里,而且不通過父子組件的方式,那麼我該怎麼做呢?實際上非常的簡單

這個時候我們就需要一個 ‘第三者來處理這個東西’,這個第三者就是這個Vuex。

  1. vueX的引入

實際上,如果你有手動的安裝使用配VueRouter的經驗的話。這Vuex也是差不多的都是一樣的使用方法

第一步:npm install vuex
第二步:創建一個文件夾sote里寫一個index.js
第三部:在index裏面安裝
第四部:在main里掛載就好了

index.js

import Vue from 'vue'
import Vuex from 'vuex'
// 安裝
Vue.use(Vuex)

// 使用
const store = new Vuex.Store({
    state:{},
    mutations: {
    },
    actions:{},
    getters:{},
    modules:{}

})

// 倒出
export default store


main.js

import Vue from 'vue'
import App from './App.vue'

// 導入
import Store from './store'

Vue.config.productionTip = false

// 掛載
new Vue({
  Store,
  render: h => h(App),
}).$mount('#app')

非常的簡單

  1. app里的業務邏輯
<template>
  <div id="app">
    <p>{{ $store.state.contuned }}</p>
    
      <button @click="$store.state.contuned ++" >+</button>
      <button @click="$store.state.contuned --" >-</button>
    
    <h1>------bmlaoli的界面--------</h1>

    <bmlao></bmlao>

  </div>
</template>

<script>

import bmlao from '@/components/Bmlaoli';

export default {
  name: 'App',
  components: {
    bmlao,
  },
  data() {
    return {
      // contuned: 100
    }
  },
}
</script>

<style>
</style>

三、正確的操作state的方式

1.需要注意的地方

$store.state.contuned

需要非常說的就是 請你不要這樣去修改vuex里的值,而是通過如下的方式去修改,詳細見官方api說明

  1. 概述我們的更改邏輯
    view視圖提交(Dispatch) —-> actions處理異步操作(commit) —–> Muations 記錄你的修改 ,方便以後追蹤(Mutate) —–> state修改(render)

  2. 代碼邏輯
    /state/index.js

    state:{
        contuned:1000
    },
    mutations: {
        increment(state){
            state.contuned++
        },
        decrement(state){
            state.contuned--
        },
    },
    actions:{},
    getters:{},
    modules:{}

/app.vue

<template>
  <div id="app">
    <p>{{ $store.state.contuned }}</p>
    
      <button @click="additon" >+</button>
      <button @click="subraction" >-</button>
    
    <h1>------bmlaoli的界面--------</h1>

    <bmlao></bmlao>

  </div>
</template>

<script>

import bmlao from '@/components/Bmlaoli';

export default {
  name: 'App',
  components: {
    bmlao,
  },
  data() {
    return {
      // contuned: 100
    }
  },
  methods: {
    additon() {
      this.$store.commit('increment')
    },
    subraction() {
      this.$store.commit('decrement')
      
    },
  },
}
</script>

<style>
</style>


  1. 除了使用this.$store.state.XXX或缺vuex的數據之外,我們還有一種方法,也是開發和工作中,比較常見的東西,那就是使用map進行各種數據的映射,它可以映射全部的vuex裏面的東西
// 假設我們現在就使用map把東西數據,state裏面的東西,映射到我們的computed裏面
improt { mapState } form 'vuex
computed {
      ...mapState( ['XXX'] )
         // 但是我們不推薦使用上面得方式,我們更加推薦使用對象器別名的方式
      ...mapState( { xCount:'Count' } )      
}

===> 這樣你就得到這些State ,除了state之外,其它的mutation 還有getter也是一樣的原理
  1. 深入立即map的映射原理,

一個優秀的程序員,不應該只是停留在會用的層面,還應該靈活的掌握其中的原理,只有掌握了原理,才能做到行雲流水的開發.工具永遠只是工具,只有自己變強才是王道

====> 我們一點點的分析,
// 1. 首先我們得computed需要接受函數
computed:{
      XXXX:() => {  return this.$stroe.state.XXX }
 }
// 2. 我們要寫一個方法mapState
function mapState( array ){
     let obj = {}
     array.forEach( stateKey => { obj[stateKey] = () => this.$store.state[stateKey] }  )
     return obj
}
// 以上就是內部的實現原理

這樣我們就能開發者工具追綜這些東西的變化了

四、核心概念解讀

vueX中有五個核心

1.單一狀態樹

  1. 管理系統 現實生活中的例子
    我們先來舉一個例子,在我們國家一個人有很多的信息會被記錄到檔案管理的各個部門,車貸房貸,身份證 ,戶口 ,結婚登記,這些信息都分佈式的存放在各個局,地產局,戶口部門……,這樣對於我們的人 來說, 我們的數據來來源就是多樣的,多數據源,但是這樣有問題,就是一起管理的時候是不好管理的,你可能需要去這個地方蓋章,去哪個地方改造,如果不通過又要重新回來蓋章,XXXX太麻煩了。
  2. vuex的管理邏輯
    在我們的vue中確確實實 ,你可以new 多個Vuex但是,我們是不推薦的,因為這樣管理起來就會非常的麻煩,我們的vuex推薦是 只使用一個vuex來管理共享的數據源,這個設計理念就是;單一數據源(也叫單一狀態樹)

2.getter

這個東西類似於計算屬性
有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數:
高階函數 ,返回函數的調用

  1. 需求,還是原來的案例,我希望我獲取的contuned的平方

當然了,你這樣也是可以的

  <h2>{{ $store.state.contuned * $store.state.contuned }}</h2>

但是很low 是不啦,如果你要寫很多很多的複雜邏輯操作,那不就涼涼了嗎,所以這裏引申出我們的getter,字面理解就是獲取的時候,對數據做一些手腳,那麼我們看看如何使用

  1. 明確一下,我們的操作基本上都是在我們的vuex文件裏面進行的

在getter裏面搞事情
store/index.js


import Vue from 'vue'
import Vuex from 'vuex'
// 安裝
Vue.use(Vuex)

// 使用
const store = new Vuex.Store({
    state:{
        contuned:1000
    },
    mutations: {
        increment(state){
            state.contuned++
        },
        decrement(state){
            state.contuned--
        },
    },
    actions:{},
    getters:{
        powerCounter(state){
            return state.contuned * state.contuned 
        }
    },
    modules:{}

})

// 倒出
export default store

使用的時候就非常簡單了
/bmlaoli.vue

  <h2>{{ $store.getters.powerCounter }}</h2>

現在我們又有了另一個需求,如果我想傳遞參數,怎麼辦,我希望我過濾出一些數據,而且我們希望我們是指定條件的過濾
這裏就涉及到我們的傳遞參數的問題了
store/index.js


  fliter(state,getters){
    console.log(getters)//這裏的getters實際上就是你的整個外面的getters對象 

  // 如果你要傳遞參數,你只能返回函數的調用
      return age => {
        state.students.filter( s => s.age >= age )
      }
    }

/bmlaoli.vue

原數據
 <h2>{{ $store.getters.students }}</h2>
過濾之後
 <h2>{{ $store.getters.fliter(40) }}</h2>

3.mutation

vuex唯一更新狀態的方式,就是在這裏,如果你要更改數據,vuex唯一的更改方式就是 mutation

3.1 概念

事件類型(函數名)
回調函數(回調函數,具體的業務代碼)

mutations: {
//      increment 事件類型
// (state){ 回調函數
            // state.contuned++
        // },
        increment(state){
            state.contuned++
        },

        decrement(state){
            state.contuned--
        },
    },

3.2 傳遞參數payload負載

  1. 單個參數
  2. 多參數(傳遞對象)

需求:我們希望點擊更改狀態的時候的時候可傳入參數
/sotre/index.js


   mutations: {
        increment(state){
            state.contuned++
        },
        decrement(state){
            state.contuned--
        },
        incrementCour(state,palyload){
            consle.log(palyload)//拿到了一個傳遞過來的對象
        }
    },

bmlaoliu.vue3

addcCount(parmas){
this.$sore.commit( 'incrementCour' ,palyload)
}

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

javascript 面向對象學習(三)——this,bind、apply 和 call

this 是 js 里繞不開的話題,也是非常容易混淆的概念,今天試着把它理一理。

this 在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值,本文僅考慮非嚴格模式。記住它總是指向一個對象對於理解它的意義很重要。this 在實際使用中,大致分為以下幾種情況:

1. 函數作為對象的方法調用時,this 指向調用該函數的對象

var obj = {
    name: 'jack',
    getName: function() {
        console.log(this === obj) // true
        console.log(this.name)  // jack
    }
}
obj.getName()

這個應該很好理解,不多說了。

2. 函數作為普通函數被調用時,this 指向全局對象。在瀏覽器中,全局對象是window。

var name = 'global'
function getName() {
    console.log(this === window) // true
    console.log(this.name) // global
}
getName()

我的理解是上面的代碼可以改寫為

window.name = 'global'
window.getName = function() {
    console.log(this === window) // true
    console.log(this.name) // global
}
window.getName()

這樣其實與情況1是一樣的,相當於函數作為對象的方法調用,只不過這裏的對象是全局對象。

《Javascript 設計模式與開發實踐》一書中有個例子如下:

window.name = 'globalName';
var myObject = {
    name: 'seven',
    getName: function(){
        return this.name
    } 
}

var getName = myObject.getName
console.log(getName())  // globalName

getName 是定義在myObject 對象中的方法,在調用getName 方法時,打印出的卻是全局對象的name,而不是myObject對象的name,這再次證明了 this 並非指向函數被聲明時的環境對象,而是指向函數被調用時的環境對象

3. 函數作為構造函數調用時,指向構造出的新對象

function Person(name) {
    this.name = name  
}

var jack = new Person('Jack')
console.log(jack.name) // Jack
var rose = new Person('Rose')
console.log(rose.name) // Rose

這裏創建了兩個不同名字的對象,打印出的name也是不一樣的,說明構造函數的 this 會根據創建對象的不同而變化。需要注意的是,如果構造函數里返回了一個Object類型的對象,那麼this會指向這個對象,而不是利用構造函數創建出的對象。我們在構造函數一章里也提到過,new 操作符所做的最後一步就是返回新對象,而如果我們顯式地返回一個對象,就會覆蓋這步操作,this也就不再指向新對象。

4. 函數作為事件處理函數調用時,指向觸發事件的元素

document.getElementById("myBtn").addEventListener("click", function(e){
    console.log(this === e.currentTarget) // true
});

5. 箭頭函數

由於箭頭函數沒有this,它的 this 是繼承父執行上下文裏面的 this。執行上下文後面再討論,現在只要知道簡單對象(非函數)是沒有執行上下文的。

var obj = {
    name:  'obj',
    getName: function() {
console.log(this) // 執行上下文里的 this
return (()=>{ console.log(this.name) }) } } var fn = obj.getName() fn() // obj

按照情況2來處理的話,this 指向全局對象,應該輸出 undefined,結果並不是。與普通函數不同,箭頭函數的 this 是在函數被聲明時決定的,而不是函數被調用時。在這裏,父執行上下文是 getName 函數,也就繼承了 getName 的 this,即 obj。

利用 bind、apply、call 改變 this 指向

bind、apply、call 都是定義在 Function 原型對象上的方法,所有函數對象都能繼承這個方法,三者都能用來改變 this 指向,我們來看看它們的聯繫與區別。

function fn() {
    console.log(this.name)
}

// bind
var bindfn = fn.bind({name: 'bind'})
bindfn() // bind // apply
fn.apply({name: 'apply'}) // apply // call
fn.call({name: 'call'}) // call

我們定義了一個函數fn,然後分別調用了它的 bind、apply、call 方法,並傳入一個對象參數,通過打印出的內容可以看到 this 被綁定到了參數對象上。bind 似乎有些不同,多了一步 bindfn() 調用,這是因為 bind 方法返回的是一個函數,不會立即執行,而調用 apply 和 call 方法會立即執行。

下面再來看一下 fn 函數存在參數的情況:

function fn(a, b, c) {
    console.log(a, b, c)
}

var bindfn = fn.bind(null, 'bind');
bindfn('A', 'B', 'C');           // bind A B

fn.apply(null, ['apply', 'A']) // apply A undefined

fn.call(null, 'call', 'A');  // bind A undefined

bindfn 打印出的結果是fn調用bind方法時的傳遞的參數加上bindfn傳遞的參數,參數 ‘C’ 被捨棄掉了。調用 apply 和 call 方法打印出的則是傳遞給它們的參數,不一樣的是,apply 的參數是一個數組(或類數組),call 則是把參數依次傳入函數。這時候再看它們的定義應該會好理解很多:

bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其餘參數將作為新函數的參數,供調用時使用。

apply() 方法調用一個具有給定 this 值的函數,以及作為一個數組(或類數組對象)提供的參數。

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

我們可以利用它們來借用其他對象的方法。已知函數的參數列表 arguments 是一個類數組對象,比如上例中函數 fn 的參數 a, b, c,因為它不是一個真正的數組,不能調用數組方法,這時借用 apply/call 方法(bind 也可以,就是用得比較少)將 this 指向 arguments 就能借用數組方法:

(function(){
    Array.prototype.push.call(arguments, 'c')
    console.log(arguments) // ['a', 'b', 'c']
})('a','b')

值得一提的是,push 方法並不是只有數組才能調用,一個對象只要滿足1.可讀寫 length 屬性;2.對象本身可存取屬性. 就可以利用 call / apply 調用 push 方法。

 

參考:

《Javascript 設計模式與開發實踐》

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

http://www.imooc.com/article/80117

https://blog.csdn.net/weixin_42519137/article/details/88053339

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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

Linux系統如何設置開機自動運行腳本?

大家好,我是良許。

在工作中,我們經常有個需求,那就是在系統啟動之後,自動啟動某個腳本或服務。在 Windows 下,我們有很多方法可以設置開機啟動,但在 Linux 系統下我們需要如何操作呢?

Linux 下同樣可以設置開機啟動,但可能需要我們敲一些命令(可能也有 UI 界面的設置方法,但我不熟,我更多是玩命令)。下面我們就介紹三種簡單但可行的開機啟動設置方法。

方法一:修改 /etc/rc.d/rc.local 文件

/etc/rc.d/rc.local 文件會在 Linux 系統各項服務都啟動完畢之後再被運行。所以你想要自己的腳本在開機后被運行的話,可以將自己腳本路徑加到該文件里。

但是,首先需要確認你有運行這個文件的權限。

$ chmod +x /etc/rc.d/rc.local

為了演示,我們創建了一個腳本,當它被執行之後,將在家目錄下寫入有特定信息的文件。

$ vim auto_run_script.sh

#!/bin/bash
date >> /home/alvin/output.txt
hostname >> /home/alvin/output.txt

保存退出后,再給它賦予可執行權限:

$ chmod +x auto_run_script.sh

然後,我們再將腳本添加到 /etc/rc.d/rc.local 文件最後一行:

$ vim /etc/rc.d/rc.local

/home/alvin/auto_run_script.sh

接下來,我們就可以試試效果了。直接重啟系統就可以了:

$ sudo reboot

重啟之後,就會在家目錄下看到腳本執行的結果了。

方法二:使用 crontab

大家知道,crontab 是 Linux 下的計劃任務,當時間達到我們設定的時間時,可以自動觸發某些腳本的運行。

我們可以自己設置計劃任務時間,然後編寫對應的腳本。但是,有個特殊的任務,叫作 @reboot ,我們其實也可以直接從它的字面意義看出來,這個任務就是在系統重啟之後自動運行某個腳本。

那它將運行的是什麼腳本呢?我們如何去設置這個腳本呢?我們可以通過 crontab -e 來設置。

$ crontab -e

@reboot /home/alvin/auto_run_script.sh

然後,直接重啟即可。運行的效果跟上面類似。

方法三:使用 systemd 服務

以上介紹的兩種方法,在任何 Linux 系統上都可以使用。但本方法僅適用於 systemd 系統。如何區分是不是 systemd 系統?很簡單,只需運行 ps aux 命令,查看 pid 為 1 的進程是不是 systemd 。

為了實現目的,我們需要創建一個 systemd 啟動服務,並把它放置在 /etc/systemd/system/ 目錄下。

我們創建的 systemd 啟動服務如下。請注意,這時後綴是 .service ,而不是 .sh

$ vim auto_run_script.service

[Unit]
Description=Run a Custom Script at Startup
After=default.target

[Service]
ExecStart=/home/alvin/auto_run_script.sh

[Install]
WantedBy=default.target

從服務的內容可以看出來,我們最終還是會調用 /home/alvin/auto_run_script.sh 這個腳本。

然後,我們再把這個腳本放置在 /etc/systemd/systerm/ 目錄下,之後我們再運行下面兩條命令來更新 systemd 配置文件,並啟動服務。

$ systemctl daemon-reload
$ systemctl enable auto_run_script.service

萬事俱備之後,我們就可以重啟系統啦。

$ reboot

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

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

※回頭車貨運收費標準

【譯】Introducing YARP Preview 1

1 YARP

    YARP是一個項目,用於創建反向代理服務器。它開始於我們注意到來自微軟內部團隊的一系列問題。他們要麼為其服務構建反向代理,要麼詢問 API 和用於構建 API 的技術。因此我們決定讓他們聚在一起開發一個通用解決方案,該解決方案形成了YARP。

    YARP是一個反向代理工具包,用於使用 ASP.NET 和 .NET 中的基礎設施在 .NET 中構建代理服務器。YARP 的主要區別是,它被設計為易於自定義和調整,以滿足不同方案的特定需求。YARP 插入ASP.NET管道以處理傳入請求,然後它擁有自己的子管道,用於執行將請求代理到後端服務器的步驟。客戶可以添加其他module,或根據需要更換常備module。

    隨着其開發已基本到位,我們製作了 YARP 的第一個正式版本(Preview 1),以便更好地協作並獲得反饋。

2 Preview 1 是什麼

    • 核心代理的基礎結構
    • 基於配置的路由定義
    • 擴展性的管道模型
    • Forwarded標頭(硬編碼)
    • 目標 .NET Core 3.1 和 .NET Core 5

3 Preview 1 不包括

    • 會話親和性(又稱會話保持)
    • Forwarded標頭(可配置)
    • 基於代碼的路由定義和預請求路由
    • 指標和日誌
    • 性能調整
    • 連接篩選

4 快速開始

Step 01 下載.net framework

    YARP 適用於 .NET Core 3.1 或 .NET 5 Preview 4(或更高版本)。

Step 02 創建一個ASP.NET Core項目

Step 03 打開項目,添加引用,確保其包含

<PropertyGroup>
    <TargetFramework>netcoreapp5.0</TargetFramework>
</PropertyGroup>

  和

<ItemGroup>
    <PackageReference Include="Microsoft.ReverseProxy" Version="1.0.0-preview.1.*" />
</ItemGroup>

Step 04 Startup.cs

  YARP 當前使用配置文件來定義代理的路由和終結點。在ConfigureServices方法中加載。

public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddReverseProxy()
        .LoadFromConfig(Configuration.GetSection("ReverseProxy"));
}

  Configure方法定義ASP.NET的請求處理管道。反向代理插入到ASP.NET的終結點路由,然後具有其自己的代理子管道。在這裏,可以添加代理管道模塊(如負載均衡)來自定義請求的處理。

/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
public void Configure(IApplicationBuilder app)
{
    app.UseHttpsRedirection();

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapReverseProxy(proxyPipeline =>
        {
            proxyPipeline.UseProxyLoadBalancing();
        });
    });
}

Step 05 配置

  YARP 的配置定義在appsettings.json中:

"ReverseProxy": {
    "Routes": [
      {
        "RouteId": "app1",
        "BackendId": "backend1",
        "Match": {
          "Methods": [ "GET", "POST" ],
          "Host": "localhost",
          "Path": "/app1/"
        }
      },
      {
        "RouteId": "route2",
        "BackendId": "backend2",
        "Match": {
          "Host": "localhost"
        }
      }
    ],
    "Backends": {
      "backend1": {
        "LoadBalancing": {
          "Mode": "Random"
        },
        "Destinations": {
          "backend1_destination1": {
            "Address": "https://example.com:10000/"
          },
          "backend1_destination2": {
            "Address": "http://example.com:10001/"
          }
        }
      },
      "backend2": {
        "Destinations": {
          "backend2_destination1": {
            "Address": "https://example.com:10002/"
          }
        }
      }
    }
  }
    • Backends:請求可以路由到的服務器群集。
    • Destinations:是用於指標、日誌記錄和會話保持的標識符。
    • Address:URL前綴(基地址)
    • Routes:根據請求的各個方面(如主機名、路徑、方法、請求標頭等)將傳入請求映射到後端群集。路由是有序的,因此,需要首先定義 app1 路由,因為 route2 將作為尚未匹配的所有路徑的 catchall。

  好啦,先介紹到這裏。

原文鏈接

  https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/?utm_source=vs_developer_news&utm_medium=referral

 

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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