2016年8月27日 星期六

Spring Data JPA (Spring Boot)

Spring Data JPA (Spring Boot)
以下將介紹如何在Spring Boot上使用Spring Data JPA

1.新增專案


2.勾選所需功能模組
其中REST Reopsitory非必要,REST Reopsitory主要可讓開發者可免撰寫Controller就可享有REST API可用


3.加入相依JAR檔案
以下2選1,步驟10介紹如何配置HikariCP

Maven
<dependency>
    <groupId>com.h2database</groupId >
    <artifactId>h2</artifactId >
    <scope>runtime</scope >
</dependency>

<dependency>
    <groupId com.zaxxer</groupId >
    <artifactId>HikariCP</artifactId >
    <version>2.4.7</version>
</dependency>



4.參數設定

application.properties
spring.datasource.driverClassName= com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?characterEncoding=UTF-8
spring.datasource.username= root
spring.datasource.password= password


5.Entity與Repositroy撰寫
為了展示方便,定義簡單點的Data Model

Entity Relationship Diagram(ERD)


Entity

org.iwlp.model.User
@Entity
@Table(name="user" )
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType. IDENTITY)
    private Long id;
   
    @Column(name = "name")
    private String name;
   
    @Column(name = "account")
    private String account;
   
    @Column(name = "password")
    private String password;

    @Column(name = "address")
    private String address;
   
    public Long getId() {
        return id ;
    }

    public void setId(Long id ) {
        this.id = id;
    }
   
    public String getName() {
        return name ;
    }

    public void setName(String name ) {
        this.name = name;
    }
   
    public String getAccount() {
        return account ;
    }

    public void setAccount(String account ) {
        this.account = account;
    }
   
    public String getPassword() {
        return password ;
    }

    public void setPassword(String password ) {
        this.password = password;
    }

    public String getAddress() {
        return address ;
    }

    public void setAddress(String address ) {
        this.address = address;
    }
}


org.iwlp.model.Product
@Entity
@Table(name="product" )
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType. IDENTITY)
    private Long id;
   
    @Column(name = "name")
    private String name;
   
    @Column(name = "description")
    private String description;
   
    @Column(name = "price")
    private int price ;
   
    @Column(name = "stock")
    private int stock ;
   
    public Long getId() {
        return id ;
    }

    public void setId(Long id ) {
        this.id = id;
    }
   
    public String getName() {
        return name ;
    }

    public void setName(String name ) {
        this.name = name;
    }
   
    public String getDescription() {
        return description ;
    }

    public void setDescription(String descript ) {
        this.description = descript;
    }
   
    public int getPrice() {
        return price ;
    }

    public void setPrice(int price) {
        this.price = price;
    }
   
    public int getStock() {
        return stock ;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }
}



Repository

org.iwlp.repository.User
@RepositoryRestResource
public interface UserReopsitory extends PagingAndSortingRepository<User, Long>{

}



org.iwlp.repository.User
@RepositoryRestResource
public interface ProductRepository extends CrudRepository<Product, Long>{

}

6.Configuration
若想使用Spring Data Rest, 請用annoation @Import(RepositoryRestMvcConfiguration.class)

org.iwlp.SpringBootJapApplication
@SpringBootApplication
@Import(RepositoryRestMvcConfiguration.class)
public class SpringBootJapApplication {

        public static void main(String[] args) {
              SpringApplication. run(SpringBootJapApplication.class, args);
       }
}



若有使用Spring Data Rest,啟動時進入http://127.0.0.1:8080/
可看到尚未撰寫Controller時,Spring Data Rest以自動幫你建立REST APIs
每個REST API Resource會根據extends CrudRepository和extends PagingAndSortingRepository有所不同

以下簡易的展示API

Resource User 第一頁所有records
http://ift.tt/2nqfopX

Resource User 中 id=1 record
http://ift.tt/2nqgZMo

若要自訂Spring Data Rest中Resource的名稱,請參考以下修改方式

org.iwlp.repository.User
@RepositoryRestResource (collectionResourceRel = "user", path = "user")
public interface UserReopsitory extends PagingAndSortingRepository<User, Long>{

}

觀看REST APIs
http://127.0.0.1:8080/



7.自訂Controller
利用@Autowired 注入Repository, Repository可用的method與extends PagingAndSortingRepository息息相關
PagingAndSortingRepository只有搜尋相關API,若為Repository extends CrudRepository,則會有CRUD APIS


org.iwlp.controller.UserController
@Api(value = "/v1/" , description = "帳戶管理" , produces = "application/json")
@RestController
@RequestMapping (value = "/api/user")
public class UserController {
    @Autowired
    UserReopsitory repository;
   
    @RequestMapping(method=RequestMethod. GET,value="/findById" )
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id" , value = "使用者Id", required = true, defaultValue = "2" ,dataType = "long", paramType = "query")
      })
    public User search(
            @RequestParam(value = "id" ) Long id
            ) {
        return repository .findOne(id);
    }
}

以Swagger UI檢視自訂的REST APIs(很遺憾Spring Data Rest自動產生的controller還沒研究如何整合swagger,所以看不到)

操作結果


8.Controller通用APIs
若是沒有用Spring Data Rest自動產生的controller,那有甚麼方式可以更快速的實作每個Table的CRUD呢?
其實每個Table功能不外乎就CRUD,只是Resource對象不同,因此可使用抽象類別撰寫好對應的CRUD APIs
再利用泛型取得指定的Resource進行操作,或是直接載入RESThub專案近來也可以(RESThub 也是透過繼承共用的CRUD類別)。

實作方式,首先寫個介面(Interface) RestController,規劃好對應的功能(CRUD),但不會此介面不會寫出實際運作方式(如何實現CRUD)
因為Interface僅是框架,讓實作的類別可被規範要做那些Method
參考REDThub source code
org.iwlp.controller
package org.iwlp.controller;


import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.util.Set;

/**
 * REST controller interface
 *
 * @param <T>  Your resource POJO to manage, maybe an entity or DTO class
 * @param <ID> Primary resource identifier at webservice level, usually Long or String
 */
public interface RestController<T, ID extends Serializable> {

    /**
     * Create a new resource<br />
     * REST webservice published : POST /
     *
     * @param resource The resource to create
     * @return CREATED http status code if the request has been correctly processed, with updated resource enclosed in the body, usually with and additional identifier automatically created by the database
     */
    @RequestMapping(method = RequestMethod. POST)
    @ResponseStatus(HttpStatus. CREATED)
    @ResponseBody
    T create( @RequestBody T resource );

    /**
     * Update an existing resource<br/>
     * REST webservice published : PUT /{id}
     *
     * @param id       The identifier of the resource to update, usually a Long or String identifier. It is explicitely provided in order to handle cases where the identifier could be changed.
     * @param resource The resource to update
     * @return OK http status code if the request has been correctly processed, with the updated resource enclosed in the body
     * @throws NotFoundException
     */
    @RequestMapping(value = "{id}", method = RequestMethod.PUT)
    @ResponseBody
    T update( @PathVariable ID id , @RequestBody T resource);

    /**
     * Find all resources, and return the full collection (plain list not paginated)<br/>
     * REST webservice published : GET /?page=no
     *
     * @return OK http status code if the request has been correctly processed, with the list of all resource enclosed in the body.
     * Be careful, this list should be big since it will return ALL resources. In this case, consider using paginated findAll method instead.
     */
    @RequestMapping(method = RequestMethod. GET, params = "page=no" )
    @ResponseBody
    Iterable<T> findAll();

    /**
     * Find all resources, and return a paginated and optionaly sorted collection<br/>
     * REST webservice published : GET /search?page=0&size=20 or GET /search?page=0&size=20&direction=desc&properties=name
     *
     * @param page       Page number starting from 0. default to 0
     * @param size       Number of resources by pages. default to 10
     * @param direction  Optional sort direction, could be "asc " or "desc"
     * @param properties Ordered list of comma separeted properies used for sorting resulats. At least one property should be provided if direction is specified
     * @return OK http status code if the request has been correctly processed, with the a paginated collection of all resource enclosed in the body.
     */
    @RequestMapping(method = RequestMethod. GET)
    @ResponseBody
    Page<T> findPaginated( @RequestParam(value = "page" , required = false, defaultValue = "1" ) Integer page,
                          @RequestParam(value = "size" , required = false, defaultValue = "10" ) Integer size,
                          @RequestParam(value = "direction" , required = false, defaultValue = "ASC" ) String direction,
                          @RequestParam(value = "properties" , required = false) String properties );

    /**
     * Find a resource by its identifier<br/>
     * REST webservice published : GET /{id}
     *
     * @param id The identifier of the resouce to find
     * @return OK http status code if the request has been correctly processed, with resource found enclosed in the body
     * @throws NotFoundException
     */
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    @ResponseBody
    T findById( @PathVariable ID id );

    /**
     * Find multiple resources by their identifiers<br/>
     * REST webservice published : GET /?ids[]=
     * <p/>
     * example : /? ids[]=1&ids []=2&ids[]=3
     *
     * @param ids List of ids to retrieve
     * @return OK http status code with list of retrieved resources. Not found resources are ignored:
     * no Exception thrown. List is empty if no resource found with any of the given ids.
     */
    @RequestMapping(method = RequestMethod. GET, params = "ids[]" )
    @ResponseBody
    Iterable<T> findByIds( @RequestParam(value = "ids[]" ) Set<ID> ids);

    /**
     * Delete all resources<br/>
     * REST webservice published : DELETE /<br/>
     * Return No Content http status code if the request has been correctly processed
     */
    @RequestMapping(method = RequestMethod. DELETE)
    @ResponseStatus(HttpStatus. NO_CONTENT)
    void delete();

    /**
     * Delete a resource by its identifier<br />
     * REST webservice published : DELETE /{id}<br />
     * Return No Content http status code if the request has been correctly processed
     *
     * @param id The identifier of the resource to delete
     * @throws NotFoundException
     */
    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus. NO_CONTENT)
    void delete( @PathVariable ID id );

}

光有Interface只是空有殼,還必須有個抽象類別替interface做完絕大部分的CRUD
RESThub的RepositoryBasedRestController有些許問題,findPaginated與findByIds共用同個API path
因此這邊改寫RepositoryBasedRestController一些功能,如果每個Table的records都需要紀錄日期,還可自行加入searchByDateTime

org.iwlp.controller.CustomRepositoryBasedRestController
package org.iwlp.controller;

import java.io.Serializable;
import java.util.List;
import java.util.Set;
import org.itri.exception.NotFoundException;
import org.iwlp.controller.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;

public abstract class CustomRepositoryBasedRestController<T, ID extends Serializable, R extends PagingAndSortingRepository > implements RestController<T, ID> {

    protected R repository;
    protected T t;

    protected Logger logger = LoggerFactory.getLogger(CustomRepositoryBasedRestController. class);

    /**
     * You should override this setter in order to inject your repository with @Inject annotation
     *
     * @param repository The repository to be injected
     */
    public void setRepository(R repository ) {
        this.repository = repository;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T create( @RequestBody T resource ) {
        return (T)this.repository .save(resource);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T update( @PathVariable ID id , @RequestBody T resource) {
        Assert. notNull(id, "id cannot be null" );

        T retrievedResource = this.findById(id);
        if (retrievedResource == null) {
            throw new NotFoundException();
        }

        return (T)this.repository .save(resource);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<T> findAll() {
        return repository.findAll();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @RequestMapping(method=RequestMethod. GET,value="/paging" )
    @ApiImplicitParams({
        @ApiImplicitParam(name = "direction" , value = "direction", required = false, defaultValue = "ASC" ,dataType = "string", paramType = "query"),
        @ApiImplicitParam(name = "properties" , value = "column name", required = false, defaultValue = "id" ,dataType = "string", paramType = "query")
    })
    public Page<T> findPaginated( @RequestParam(value = "page" , required = false, defaultValue = "1" ) Integer page,
                                 @RequestParam(value = "size" , required = false, defaultValue = "10" ) Integer size,
                                 @RequestParam(value = "direction" , required = false, defaultValue = "" ) String direction,
                                 @RequestParam(value = "properties" , required = false) String properties ) {
        Assert. isTrue(page > 0, "Page index must be greater than 0" );
        Assert. isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC");
        if(direction .isEmpty()) {
            return this.repository.findAll(new PageRequest(page - 1, size));
        } else {
            Assert. notNull(properties);
            return this.repository.findAll(new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString( direction.toUpperCase()), properties.split( ","))));
        }
    }
   

    /**
     *
     * @param columnName : 欄位名稱
     * @return T
     */

    @RequestMapping(method=RequestMethod. GET,value="/findLast" )
    public @ResponseBody T findLastRecord(@RequestParam (value = "columnName", required = true , defaultValue = "id") String columnName) {

        final PageRequest pageable = new PageRequest(
                0, 1, new Sort(
                    new Order(Direction.DESC, columnName)
                  )
                );
        List<T> list = this.repository.findAll(pageable ).getContent();
        if(list .size() > 0){
            return (T) this.repository .findAll(pageable).getContent().get(0);
        } else {
            return null;
        }
    }
   
    /**
     * {@inheritDoc}
     */
    @Override
    public T findById( @PathVariable ID id ) {
        T entity = (T)this.repository .findOne(id) ;
        if (entity == null) {
            throw new NotFoundException();
        }

        return entity ;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<T> findByIds( @RequestParam(value="ids[]" ) Set<ID> ids){
        Assert. notNull(ids, "ids list cannot be null" );
        return this.repository.findAll(ids );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete() {
        Iterable<T> list = repository.findAll();
        for (T entity : list) {
            repository.delete(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(@PathVariable ID id) {
        T resource = this.findById(id);
        this.repository.delete( resource);
    }


}



User Controller修改

org.iwlp.controller.UserController
@Api(value = "/v1/" , description = "帳戶管理" , produces = "application/json")
@RestController
@RequestMapping (value = "/api/user")
public class UserController extends CustomRepositoryBasedRestController<User, Long, UserReopsitory>{
    @Autowired
    @Override
    public void setRepository(UserReopsitory repository){
        this.repository = repository;
    }
   
    @RequestMapping(method=RequestMethod. GET,value="/findById" )
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id" , value = "使用者Id", required = true, defaultValue = "2" ,dataType = "long", paramType = "query")
      })
    public User search(
            @RequestParam(value = "id" ) Long id
            ) {
        return repository .findOne(id);
    }
}




使用Swagger UI檢視所有REST APIs
可看到繼承的CRUD REST APIs

9.Spring data實作更複雜的SQL

User Repository加入所需要的功能
可以使用Spring Data方式或用@Query的方式定義複雜的JPQL查詢資料

org.iwlp.repository.User
@RepositoryRestResource (collectionResourceRel = "user", path = "user")
public interface UserReopsitory extends PagingAndSortingRepository<User, Long>{

    public User findFirstByNameAndAccount(String name, String account );
   
    @Query( "SELECT u FROM User u WHERE u.name=?1 AND u.account=?2" )
    public User search(String name, String account );
  
}




User Controller修改,加入自訂的APIs

org.iwlp.controller.UserController
@Api(value = "/v1/" , description = "帳戶管理" , produces = "application/json")
@RestController
@RequestMapping (value = "/api/user")
public class UserController extends CustomRepositoryBasedRestController<User, Long, UserReopsitory>{
    @Autowired
    @Override
    public void setRepository(UserReopsitory repository){
        this.repository = repository;
    }
   
    @RequestMapping(method=RequestMethod. GET,value="/findById" )
    @ApiImplicitParams({
        @ApiImplicitParam(name = "id" , value = "使用者Id", required = true, defaultValue = "2" ,dataType = "long", paramType = "query")
      })
    public User search(
            @RequestParam(value = "id" ) Long id
            ) {
        return repository .findOne(id);
    }
}



最後再來檢視Swagger UI

10.改用Hikari DB Conn. Pool(非必要功能)

先修改Spring boot設定檔

application.properties
#Hikari
spring.datasource.driverClassName=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
spring.datasource.username=root
spring.datasource.password=password
spring.dataSource.databaseName=testdb
spring.dataSource.portNumber=3306
spring.dataSource.serverName=localhost


在建立Datasource configuration,目的當然是要配置Hikari

org.iwlp.config
@Configuration
public class DataSourceConfig {
    private static final Logger log = LoggerFactory.getLogger(DataSourceConfig. class);
           
    @Value( "${spring.datasource.username}" )
    private String user;

    @Value( "${spring.datasource.password}" )
    private String password;

    @Value( "${spring.dataSource.portNumber}" )
    private String portNumber;
   
    @Value( "${spring.dataSource.serverName}" )
    private String serverIp;
   
    @Value( "${spring.dataSource.databaseName}" )
    private String databaseName;
   
    @Value( "${spring.datasource.driverClassName}" )
    private String driverClassName;

    @Bean
    public DataSource primaryDataSource() {
       
        log.debug("ClassName:{}" , driverClassName);
        log.debug("ServerIp:{}" , serverIp);
        log.debug("PortNumber:{}" , portNumber);
        log.debug("user:{}" , user);
        log.debug("password:{}" , password);
       
        HikariConfig config = new HikariConfig();     
        config.setDataSourceClassName(driverClassName );
        config.addDataSourceProperty("url" , "jdbc:mysql://" +serverIp+ ":"+portNumber +"/"+ databaseName+"?characterEncoding=UTF-8" );
        config.addDataSourceProperty("user" , user);
        config.addDataSourceProperty("password" , password);
        config.addDataSourceProperty("cachePrepStmts" , "true");
        config.addDataSourceProperty("prepStmtCacheSize" , "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit" , "2048");
        HikariDataSource ds = new HikariDataSource(config);
        return ds ;
    }
}


11.下載專案



參考資料
RESThub



Tags: Spring, Spring-Data-JPA, Spring Boot, IFTTT-SYNC
August 27, 2016 at 03:45PM
Open in Evernote

2016年8月26日 星期五

SpringMVC + SpringFox(Swagger2.0) + Spring Boot

以下將介紹如何在Spring Boot上使用Springfox Swagger2.0

IDE: Eclipse Neon Release (4.6.0)
Plugin: STS

1.新增專案










2.勾選所需功能模組(Web)










下一步



建立專案中



產生的專案目錄結構














3.加入相依jar檔


Maven
<dependency>
    <groupId> io.springfox</groupId >
    <artifactId> springfox-swagger2 </artifactId>
    <version> 2.5.0</version >
</dependency>


Gradle
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0'



4.建立Swagger配置

org.itri.config.Swagger2SpringBoot.java
@SpringBootApplication
@EnableSwagger2
@ComponentScan ("org.iwlp.controller")
public class Swagger2SpringBoot {
   
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Swagger2SpringBoot. class, args);
      }

      @Bean
      public Docket newsApi() {
          return new Docket(DocumentationType. SWAGGER_2)
                  .apiInfo(apiInfo())
                  .select()
                  .build();
      }
      
      private ApiInfo apiInfo() {
          return new ApiInfoBuilder()
                  .title( "Spring REST Sample with Swagger" )
                  .description( "Spring REST Sample with Swagger" )
                  .version( "2.0")
                  .build();
      }
}



5.建立Controller

org.iwlp.controller.TestController
@RestController
@RequestMapping (value = "/api")
public class TestController {
    private static final Logger log = LoggerFactory.getLogger(TestController. class);
   
    @RequestMapping(method=RequestMethod. GET,value="/hello" )
    public @ResponseBody Object sayHello(
            @RequestParam(required = false, value = "message") String message
            ) {
        log.debug("{}", "message");
        return "Controller HELLO:" + message;
    }
}


6.如何使用SwaggerUI
這時啟動Spring Boot
API DOC:http://127.0.0.1:8080/v2/api-docs
可以看到REST API都使用JSON所描述
並可用呼叫此REST API:http://127.0.0.1:8080/api/hello?message=hi

找個SwaggerUI把API DOC網址貼上即可看到專案底下所有REST API


若是本機的SwaggerUI如何呈現
先去Swagger官方網站下載UI


















以下範例將SwaggerUI(v2.1.4) dist 資料夾重新命名為apis
















重啟Spring Boot後,再用瀏覽器進入SwaggerUI
SwaggerUI : http://127.0.0.1:8080/apis/index.html







可看到自訂的REST APIs













7.如何修改REST API的描述、預設值?

org.iwlp.controller.TestController
@RestController
@RequestMapping (value = "/api")
public class TestController {
    private static final Logger log = LoggerFactory.getLogger(TestController. class);
   
    @RequestMapping(method=RequestMethod. GET,value="/hello" )
    @ApiImplicitParams({
        @ApiImplicitParam(name = "message" , value = "輸入文字訊息", required = true, defaultValue = "Hi,Swagger" ,dataType = "string", paramType = "query")
      })
    public @ResponseBody Object sayHello(
            @RequestParam(value = "message" ) String message
            ) {
        log.debug("{}" , "message");
        return "Controller HELLO:" + message;
    }
}



設定後結果





8.下載專案

參考資料:
Springfox Reference Documentation

Usage of Swagger 2.0 in Spring Boot Applications to document APIs


2016年8月2日 星期二

如何在SwaggerUI上提供下拉選單參數

如何在SwaggerUI上提供下拉選單參數
雖然透過Swagger annotation可以在Swagger UI上透過文字輸入元件將參數填上
但若參數只能接受部分特定的字串怎麼辦?

程式碼

Swagger UI

透過Enumeration(列舉)實現Swagger UI 下拉選單參數

Enumeration class



Spring Controller
原先用String的部分改成列舉


Swagger UI



當然也可以用中文
Enumeration class


Spring Controller

Swagger UI


Tags: REST, Swagger, Swagger UI, IFTTT-SYNC
August 02, 2016 at 09:32AM
Open in Evernote

2016年7月28日 星期四

使用Git產生key,並透過private key獲取專案內容

使用Git產生key,並透過private key獲取專案內容
1.下載與安裝
先去官網下載git



2.產生公私鑰
安裝完畢後,開啟GIT GUI



Git GUI選單中按下Help


選擇Show SSH Key



按下Generate Key以產生新的key



輸入進入密碼,可不輸入直接按ok


最後產生的key如下圖(key圖片有被我遮蔽囉)


3.檢驗公私鑰是否已產生
進入 C:\使用者\{使用者帳號},可看到新增.ssh資料夾



進去後就可以看到KEY

若官方GUI使用不習慣,可下載TortoiseGit


4.透過獲取專案內容時指定私鑰
安裝完TortoiseGit後重開機,重開後再進入PuTTYgen

進入PuTTYgen主畫面



選擇清單中的File > Load private key



選擇你放KEY的目錄,預設為 C:\使用者\{使用者帳號}\.ssh
再選擇剛產生的私鑰



按下Save Private Key以將私鑰存成ppk檔案



再選擇私鑰存放的路徑


之後再Clone時,可填上方才的私鑰


當然產生公私鑰也完全可透過PuTTYgen產生


Tags: IWLP, Tutorial, GIT, IFTTT-SYNC
July 28, 2016 at 09:17AM
Open in Evernote

2016年7月27日 星期三

Crome瀏覽器解決Cross-Origin Resource Sharing (CORS)

Crome瀏覽器解決Cross-Origin Resource Sharing (CORS)
因為 Javascript 本身對安全性的限制, 無法抓取本身網域(Domain )以外的資料
瀏覽器console中會看到相關的錯誤訊息


有哪些情況下是不允許的?

解決方法
1. AJAX Proxy
2. JSONP
3. CORS(Cross-Origin Resource Sharing)


本篇介紹Chrome瀏覽器中CORS擴充元件,或始可用別的方式實作CORS(瀏覽器需支援CORS)
以下是CORS的作業流程,簡略描述



Chrome中如何安裝CORS?

只要在Chrome線上應用程式透過關鍵字搜尋,並將 Allow-Control-Allow-Origin: * 加到Chorme中



即可在瀏覽器右上角看到此擴充套件,點擊紅色cors按鈕
並開啟Enable cross-origin resource sharing

                                                                                    

當紅色cors按鈕變成綠色即表示可以cross-domain請求


                                                                                      


參考資料




Tags: IWLP, Tutorial, Cross Domain, CORS, IFTTT-SYNC
July 27, 2016 at 04:17PM
Open in Evernote