全球氣候暖化,台南海岸遭到侵蝕,沙洲流失,潟湖面積縮小,台南市政府已展開復育工程

摘錄自2019年11月29日中央通訊社報導

全球氣候暖化,台南海岸遭到侵蝕,沙洲流失,潟湖面積縮小,台南市政府已展開復育工程,8年來有效減緩海岸退縮,也讓沙洲及潟湖得以存續。

台南市政府水利局人員今天(29日)指出,台南縣市合併後,水利局已在沿海的北門區及七股區辦理30件沙洲復育工程,總經費約新台幣2億6000萬元,截至去年,8年來復育沙洲面積約78公頃,長度約16公里,疏濬潟湖土方約93萬立方公尺。

水利局復育沙洲,先從減少外在侵蝕開始,利用竹樁、海事固袋及沙腸袋等近自然工法,消減波浪及暴潮能量,減緩沙洲侵蝕,另再加強沙洲內在的防護強度,以抽砂養灘、編籬定砂及搭配植生復育,以加高培厚沙洲,還能延長潟湖壽命。

以七股區青山港沙洲來說,此地沙洲民國83年至98年期間,每年以平均50公尺的速度退縮,七股潟湖面積也越來越小,但在執行復育工程後,退縮速度大幅減少,從每年平均50公尺減緩至6公尺。

若是比較105年至107年變化,七股區青山港沙洲全線穩定,甚至部分區域有往外海成長的趨勢,顯示沙洲復育已有初步成效。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※各大品牌中古空壓機買賣情報站

※【找工作】徵求中部倉庫堆高機人員

※選用哪種桶裝水,外宿露營超方便?

連續封口機購物網-不怕你比價,就怕你買貴!

塑膠射出成型不良品原因及改善對策 !

澎湖成功國小師生認養成功水庫水門出口處的紅樹林,定期的淨灘

摘錄自2019年11月29日中央通訊社報導

澎湖成功國小師生認養成功水庫水門出口處的紅樹林,定期的淨灘,讓紅樹林有一個乾淨的家外,利用淨灘所撿拾漂流木或浮球,帶回學校資源再利用,發想動手玩創意,佈置於校園之中。

澎湖成功國小校長朱劍忠表示,成功國小位於港底海水渠道的盡頭,也是成功水庫洩洪排水的出海口,海漂垃圾匯集與停留,學校師生每年進行環境教育,都會在此進行淨灘,今年在海邊淨灘時,意外發現有幾棵矮小的紅樹林,引起師生的好奇,師生為成功紅樹林許下承諾,讓紅樹林有一個乾淨的家。

朱劍忠表示,澎湖青螺及菜園2處濕地的有紅樹林,已列入保護,而成功海域的紅樹林,目前數量不多,但學校的環教育也將從這裡出發,進行觀察、測量、實驗與紀錄,讓小朋友們了解紅樹林的背景知識,每學期定期淨灘,讓紅樹林快快長大。

朱劍忠表示,學校6年級的蕭可欣與王昱讚2名小朋友,利用淨灘所撿拾漂流木或浮球,帶回學校資源再利用,發想動手玩創意,打造一座與學校同名的「成功燈塔」等作品,佈置於校園之中,還將創作過程之所見、所聞、所思,以海洋故事繪本「苳苳漂流記」來具體呈現,營造具特色的成功學園。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

噴霧洗滌塔實際應用案例分享

※想知道CNC 自動車床與CNC車床有何區別??

示波器鮮為人知的使用技巧?

※客製專屬滑鼠墊、可愛造型L夾L型資料夾、透明證件套、手提袋,專業印刷設計廠商!  

※使用真空封口機常見問題?

宜蘭縣鰻苗捕獲量不如預期,有些捕鰻苗人把矛頭指向鄰近的壯圍污水處理廠(水資源回收中心)

摘錄自2019年12月2日自由時報宜蘭報導

宜蘭縣鰻苗捕獲量不如預期,有些捕鰻苗人把矛頭指向鄰近的壯圍污水處理廠(水資源回收中心),質疑未完成淨化便排入蘭陽溪流到出海口,導致鰻苗死亡;縣府解​​釋,污水都符合放流水標準,不會有污染河川情形。

遊姓漁民說,壯圍污水處理廠污水經管線排入蘭陽溪,她憂心排放污水不符標準,造成鰻苗死亡或不靠岸;另陳姓漁民補充,最近出海口抓到的鰻苗,身上發現奇怪斑點,疑似碰到污水所致。

壯圍鄉新南村長葉慶文也發現過有斑點鰻苗,他認為,若遭污染早就暴斃,不會活蹦亂跳,曾詢問過盤商,推測是其他種類鰻苗,與污水無關,今年苗況差肇因於氣候影響。

縣府回應,壯圍污水廠廢水經過處理,必須符合放流水標準排放,不會有污染河川等問題。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

飲水機設備有哪些?

※如何選購橡膠製品橡膠按鍵規格有哪些?該如何應用在商品上?  

AVX DistributorAVX TPSAVX鉭質電容器 規格有哪些?各別作用在於?

nbr乳膠手套可適用在什麼環境?

※空壓機何時可換油? 空壓機保養的正確觀念與維護 !

真空封口機該不該買?使用心得分享

海巡署金馬澎分署第一○岸巡隊第一機動巡邏站今天(30日)執行灘岸搜索勤務時

摘錄自2019年11月30日中央通訊社報導

海巡署金馬澎分署第一○岸巡隊第一機動巡邏站今天(30日)執行灘岸搜索勤務時,在東引北澳口岸際沙灘發現一隻死亡海豚,連江縣產發處確認為瀕危「微笑天使」露脊鼠海豚,並協助就地掩埋。

第一○岸巡隊表示,東引出現死亡鯨豚並不多見,今天發現的露脊鼠海豚長度約100公分,寬度約25公分,由岸巡隊拍照存證,並由產發處漁牧科協處。

露脊鼠海豚全球僅剩1000隻左右,在台灣屬於保育類動物,已非常少見。海豚可能在海中尋找食物遭其它生物追趕,或為確保下一代延續,依本能至近岸生產,但因近岸海漂垃圾過多誤食,遭受生命威脅。

本站聲明:網站內容來源於https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道台中食品倉有哪些?  

堆高機基本駕駛指導教學(影片)

廢氣洗滌塔,叫得動, 找得到的專業廠商‎

※市面十大品牌封口機!該如何選購?

塑膠射出成型加工商品有哪些?

[springboot 開發單體web shop] 6. 商品分類和輪播廣告展示

目錄

商品分類&輪播廣告

因最近又被困在了OSGI技術POC,更新進度有點慢,希望大家不要怪罪哦。

我們實現了登錄之後前端的展示,如:

接着,我們來實現左側分類欄目的功能。

商品分類|ProductCategory

從上圖我們可以看出,商品的分類其實是有層級關係的,而且這種關係一般都是無限層級。在我們的實現中,為了效果的展示,我們僅僅是展示3級分類,在大多數的中小型電商系統中,三級分類完全足夠應對SKU的分類。

需求分析

先來分析分類都包含哪些元素,以jd為例:

  • logo(logo) 有的分類文字前面會有小標
  • 分類展示主圖(img_url)
  • 主標題(title)
  • 副標題/Slogan
  • 圖片跳轉地址(img_link_url)– 大多數時候我們點擊分類都會分類Id跳轉到固定的分類商品列表展示頁面,但是在一些特殊的場景,比如我們要做一個活動,希望可以點擊某一個分類的主圖直接定位到活動頁面,這個url就可以使用了。
  • 上級分類(parent_id)
  • 背景色(bg_color)
  • 順序(sort)
  • 當前分類級別(type)

開發梳理

在上一小節,我們簡單分析了一下要實現商品分類的一些points,那麼我們最好在每次拿到需求【開發之前】,對需求進行拆解,然後分解開發流程,這樣可以保證我們更好的理解需求,以及在開發之前發現一部分不合理的需求,並且如果需求設計不合理的話,開發人員完全有權,也有責任告知PM。大家的終極目的都是為了我們做的產品更加合理,好用,受歡迎!

  • 首次展示,僅僅讀取一級分類(Root)
  • 根據一級分類查詢二三級子分類

編碼實現

查詢一級分類

Service實現

1.在com.liferunner.service中創建service 接口ICategoryService.java, 編寫查詢所有一級分類的方法getAllRootCategorys,如下:

package com.liferunner.service;
import com.liferunner.dto.CategoryResponseDTO;
import com.liferunner.dto.SecondSubCategoryResponseDTO;
import java.util.List;
/**
 * ICategoryService for : 分類service
 *
 * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
 * @since 2019/11/13
 */
public interface ICategoryService {
    /**
     * 獲取所有有效的一級分類(根節點)
     *
     * @return
     */
    List<CategoryResponseDTO> getAllRootCategorys();
}

2.編寫實現類com.liferunner.service.ICategoryService.java

@Service
@Slf4j
public class CategorySericeImpl implements ICategoryService {
    @Autowired
    private CategoryMapper categoryMapper;
    
    @Override
    public List<CategoryResponseDTO> getAllRootCategorys() {
        Example example = new Example(Category.class);
        val conditions = example.createCriteria();
        conditions.andEqualTo("type", CategoryTypeEnum.ROOT.type);
        val categoryList = this.categoryMapper.selectByExample(example);
        //聲明返回對象
        List<CategoryResponseDTO> categoryResponseDTOS = new ArrayList<>();
        if (!CollectionUtils.isEmpty(categoryList)) {
            //賦值
            CategoryResponseDTO dto;
            for (Category category : categoryList) {
                dto = new CategoryResponseDTO();
                BeanUtils.copyProperties(category, dto);
                categoryResponseDTOS.add(dto);
            }
        }
        return categoryResponseDTOS;
    }
}

上述代碼很好理解,創建tk.mybatis.mapper.entity.Example,將條件傳入,然後使用通用Mapper查詢到type=1的一級分類,接着將查到的對象列錶轉換為DTO對象列表。

Controller實現

一般情況下,此類查詢都會出現在網站的首頁,因此我們來創建一個com.liferunner.api.controller.IndexController,並對外暴露一個查詢一級分類的接口:

package com.liferunner.api.controller;

import com.liferunner.service.ICategoryService;
import com.liferunner.service.IProductService;
import com.liferunner.service.ISlideAdService;
import com.liferunner.utils.JsonResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.util.Collections;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
 * IndexController for : 首頁controller
 *
 * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
 * @since 2019/11/12
 */
@RestController
@RequestMapping("/index")
@Api(value = "首頁信息controller", tags = "首頁信息接口API")
@Slf4j
public class IndexController {
    @Autowired
    private ICategoryService categoryService;

    @GetMapping("/rootCategorys")
    @ApiOperation(value = "查詢一級分類", notes = "查詢一級分類")
    public JsonResponse findAllRootCategorys() {
        log.info("============查詢一級分類==============");
        val categoryResponseDTOS = this.categoryService.getAllRootCategorys();
        if (CollectionUtils.isEmpty(categoryResponseDTOS)) {
            log.info("============未查詢到任何分類==============");
            return JsonResponse.ok(Collections.EMPTY_LIST);
        }
        log.info("============一級分類查詢result:{}==============", categoryResponseDTOS);
        return JsonResponse.ok(categoryResponseDTOS);
    }
}
Test API

編寫完成之後,我們需要對我們的代碼進行測試驗證,還是通過使用RestService插件來實現,當然,大家也可以通過Postman來測試。

{
  "status": 200,
  "message": "OK",
  "data": [
    {
      "id": 1,
      "name": "煙酒",
      "type": 1,
      "parentId": 0,
      "logo": "img/cake.png",
      "slogan": "吸煙受害健康",
      "catImage": "http://www.life-runner.com/shop/category/cake.png",
      "bgColor": "#fe7a65"
    },
    {
      "id": 2,
      "name": "服裝",
      "type": 1,
      "parentId": 0,
      "logo": "img/cookies.png",
      "slogan": "我選擇我喜歡",
      "catImage": "http://www.life-runner.com/shop/category/cookies.png",
      "bgColor": "#f59cec"
    },
    {
      "id": 3,
      "name": "鞋帽",
      "type": 1,
      "parentId": 0,
      "logo": "img/meat.png",
      "slogan": "飛一般的感覺",
      "catImage": "http://www.life-runner.com/shop/category/meat.png",
      "bgColor": "#b474fe"
    }
  ],
  "ok": true
}

根據一級分類查詢子分類

因為根據一級id查詢子分類的時候,我們是在同一張表中做自連接查詢,因此,通用mapper已經不適合我們的使用,因此我們需要自定義mapper來實現我們的需求。

自定義Mybatis Mapper實現

在之前的編碼中,我們都是使用的插件幫我們實現的通用Mapper,但是這種查詢只能處理簡單的單表CRUD,一旦我們需要SQL 包含一部分邏輯處理的時候,那就必須得自己來編寫了,let’s code.
1.在項目mscx-shop-mapper中,創建一個新的custom package,在該目錄下創建自定義mappercom.liferunner.custom.CategoryCustomMapper

public interface CategoryCustomMapper {
    List<SecondSubCategoryResponseDTO> getSubCategorys(Integer parentId);
}

2.resources目錄下創建目錄mapper.custom,以及創建和上面的接口相同名稱的XML文件mapper/custom/CategoryCustomMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liferunner.custom.CategoryCustomMapper">
    <resultMap id="subCategoryDTO" type="com.liferunner.dto.SecondSubCategoryResponseDTO">
        <id column="id" jdbcType="INTEGER" property="id"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
        <result column="type" jdbcType="INTEGER" property="type"/>
        <result column="parentId" jdbcType="INTEGER" property="parentId"/>
        <collection property="thirdSubCategoryResponseDTOList" ofType="com.liferunner.dto.ThirdSubCategoryResponseDTO">
            <id column="subId" jdbcType="INTEGER" property="subId"/>
            <result column="subName" jdbcType="VARCHAR" property="subName"/>
            <result column="subType" jdbcType="INTEGER" property="subType"/>
            <result column="subParentId" jdbcType="INTEGER" property="subParentId"/>
        </collection>
    </resultMap>
    <select id="getSubCategorys" resultMap="subCategoryDTO" parameterType="INTEGER">
        SELECT p.id as id,p.`name` as `name`,p.`type` as `type`,p.father_id as parentId,
        c.id as subId,c.`name` as subName,c.`type` as subType,c.parent_id as subParentId
        FROM category p
        LEFT JOIN category c
        ON p.id = c.parent_id
        WHERE p.parent_id = ${parentId};
    </select>
</mapper>

TIPS
上述創建的package,一定要在項目的啟動類com.liferunner.api.ApiApplication中修改@MapperScan(basePackages = { "com.liferunner.mapper", "com.liferunner.custom"}),如果不把我們的custom package加上,會造成掃描不到而報錯。

在上面的xml中,我們定義了兩個DTO對象,分別用來處理二級和三級分類的DTO,實現如下:

@Data
@ToString
public class SecondSubCategoryResponseDTO {
    /**
     * 主鍵
     */
    private Integer id;

    /**
     * 分類名稱
     */
    private String name;

    /**
     * 分類類型
     1:一級大分類
     2:二級分類
     3:三級小分類
     */
    private Integer type;

    /**
     * 父id
     */
    private Integer parentId;

    List<ThirdSubCategoryResponseDTO> thirdSubCategoryResponseDTOList;
}
---
    
@Data
@ToString
public class ThirdSubCategoryResponseDTO {
    /**
     * 主鍵
     */
    private Integer subId;

    /**
     * 分類名稱
     */
    private String subName;

    /**
     * 分類類型
     1:一級大分類
     2:二級分類
     3:三級小分類
     */
    private Integer subType;

    /**
     * 父id
     */
    private Integer subParentId;
}
Service實現

編寫完自定義mapper之後,我們就可以繼續編寫service了,在com.liferunner.service.ICategoryService中新增一個方法:getAllSubCategorys(parentId).如下:

public interface ICategoryService {
    ...
    /**
     * 根據一級分類獲取子分類
     *
     * @param parentId 一級分類id
     * @return 子分類list
     */
    List<SecondSubCategoryResponseDTO> getAllSubCategorys(Integer parentId);
}

com.liferunner.service.impl.CategorySericeImpl實現上述方法:

@Service
@Slf4j
public class CategorySericeImpl implements ICategoryService {
    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private CategoryCustomMapper categoryCustomMapper;
    ...
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<SecondSubCategoryResponseDTO> getAllSubCategorys(Integer parentId) {
        return this.categoryCustomMapper.getSubCategorys(parentId);
    }
}
Controller實現
@RestController
@RequestMapping("/index")
@Api(value = "首頁信息controller", tags = "首頁信息接口API")
@Slf4j
public class IndexController {
    @Autowired
    private ICategoryService categoryService;
    ...
    @GetMapping("/subCategorys/{parentId}")
    @ApiOperation(value = "查詢子分類", notes = "根據一級分類id查詢子分類")
    public JsonResponse findAllSubCategorys(
            @ApiParam(name = "parentId", value = "一級分類id", required = true)
            @PathVariable Integer parentId) {
        log.info("============查詢id = {}的子分類==============", parentId);
        val categoryResponseDTOS = this.categoryService.getAllSubCategorys(parentId);
        if (CollectionUtils.isEmpty(categoryResponseDTOS)) {
            log.info("============未查詢到任何分類==============");
            return JsonResponse.ok(Collections.EMPTY_LIST);
        }
        log.info("============子分類查詢result:{}==============", categoryResponseDTOS);
        return JsonResponse.ok(categoryResponseDTOS);
    }
}
Test API
{
  "status": 200,
  "message": "OK",
  "data": [
    {
      "id": 11,
      "name": "國產",
      "type": 2,
      "parentId": 1,
      "thirdSubCategoryResponseDTOList": [
        {
          "subId": 37,
          "subName": "中華",
          "subType": 3,
          "subParentId": 11
        },
        {
          "subId": 38,
          "subName": "冬蟲夏草",
          "subType": 3,
          "subParentId": 11
        },
        {
          "subId": 39,
          "subName": "南京",
          "subType": 3,
          "subParentId": 11
        },
        {
          "subId": 40,
          "subName": "雲煙",
          "subType": 3,
          "subParentId": 11
        }
      ]
    },
    {
      "id": 12,
      "name": "外煙",
      "type": 2,
      "parentId": 1,
      "thirdSubCategoryResponseDTOList": [
        {
          "subId": 44,
          "subName": "XXXXX",
          "subType": 3,
          "subParentId": 12
        },
        {
          "subId": 45,
          "subName": "RRRRR",
          "subType": 3,
          "subParentId": 12
        }
      ]
    }
  ],
  "ok": true
}

以上我們就已經實現了和jd類似的商品分類的功能實現。

輪播廣告|SlideAD

需求分析

這個就是jd或者tb首先的最頂部的廣告圖片是一樣的,每隔1秒自動切換圖片。接下來我們分析一下輪播圖中都包含哪些信息:

  • 圖片(img_url)是最基本的
  • 圖片跳轉連接(img_link_url),這個是在我們點擊這個圖片的時候需要跳轉到的頁面
  • 有的可以直接跳轉到商品詳情頁面
  • 有的可以直接跳轉到某一分類商品列表頁面
  • 輪播圖的播放順序(sort)

開發梳理

直接查詢出所有的有效的輪播圖片,並且進行排序

編碼實現

Service 實現

和商品分類實現一樣,在mscx-shop-service中創建com.liferunner.service.ISlideAdService並實現,代碼如下:

public interface ISlideAdService {
    /**
     * 查詢所有可用廣告並排序
     * @param isShow
     * @return
     */
    List<SlideAdResponseDTO> findAll(Integer isShow, String sortRanking);
}
@Service
@Slf4j
public class SlideAdServiceImpl implements ISlideAdService {

    // 注入mapper
    private final SlideAdsMapper slideAdsMapper;

    @Autowired
    public SlideAdServiceImpl(SlideAdsMapper slideAdsMapper) {
        this.slideAdsMapper = slideAdsMapper;
    }

    @Override
    public List<SlideAdResponseDTO> findAll(Integer isShow, String sortRanking) {
        Example example = new Example(SlideAds.class);
        //設置排序
        if (StringUtils.isBlank(sortRanking)) {
            example.orderBy("sort").asc();
        } else {
            example.orderBy("sort").desc();
        }
        val conditions = example.createCriteria();
        conditions.andEqualTo("isShow", isShow);
        val slideAdsList = this.slideAdsMapper.selectByExample(example);
        //聲明返回對象
        List<SlideAdResponseDTO> slideAdResponseDTOList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(slideAdsList)) {
            //賦值
            SlideAdResponseDTO dto;
            for (SlideAds slideAds : slideAdsList) {
                dto = new SlideAdResponseDTO();
                BeanUtils.copyProperties(slideAds, dto);
                slideAdResponseDTOList.add(dto);
            }
        }
        return slideAdResponseDTOList;
    }
}

從上述可以看到,這裏我使用的是構造函數注入SlideAdsMapper,其餘代碼單表查詢沒什麼特別的,根據條件查詢輪播圖,並返回結果,返回的對象是com.liferunner.dto.SlideAdResponseDTO列表,代碼如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "輪播廣告返回DTO", description = "輪播廣告返回DTO")
public class SlideAdResponseDTO{
    /**
     * 主鍵
     */
    private String id;

    /**
     * 圖片地址
     */
    private String imageUrl;

    /**
     *  背景顏色
     */
    private String backgroundColor;

    /**
     * 商品id
     */
    private String productId;

    /**
     * 商品分類id
     */
    private String catId;

    /**
     * 圖片跳轉URL
     */
    private String imageLinkUrl;

    /**
     * 輪播圖類型 用於判斷,可以根據商品id或者分類進行頁面跳轉,1:商品 2:分類 3:鏈接url
     */
    private Integer type;

    /**
     * 輪播圖展示順序 輪播圖展示順序,從小到大
     */
    private Integer sort;

    /**
     * 是否展示 是否展示,1:展示    0:不展示
     */
    private Integer isShow;

    /**
     * 創建時間 創建時間
     */
    private Date createTime;

    /**
     * 更新時間 更新
     */
    private Date updateTime;
}

Controller實現

com.liferunner.api.controller.IndexController中,新添加一個查詢輪播圖API,代碼如下:

    @Autowired
    private ISlideAdService slideAdService;

    @GetMapping("/slideAds")
    @ApiOperation(value = "查詢輪播廣告", notes = "查詢輪播廣告接口")
    public JsonResponse findAllSlideList() {
        log.info("============查詢所有輪播廣告,isShow={},sortRanking={}=============="
                , 1, "desc");
        val slideAdsList = this.slideAdService.findAll(1, "desc");
        if (CollectionUtils.isEmpty(slideAdsList)) {
            log.info("============未查詢到任何輪播廣告==============");
            return JsonResponse.ok(Collections.EMPTY_LIST);
        }
        log.info("============輪播廣告查詢result:{}=============="
                , slideAdsList);
        return JsonResponse.ok(slideAdsList);
    }

Test API

{
  "status": 200,
  "message": "OK",
  "data": [
    {
      "id": "slide-100002",
      "imageUrl": "http://www.life-runner.com/2019/11/CpoxxF0ZmH6AeuRrAAEZviPhyQ0768.png",
      "backgroundColor": "#55be59",
      "productId": "",
      "catId": "133",
      "type": 2,
      "sort": 2,
      "isShow": 1,
      "createTime": "2019-10-11T21:33:01.000+0000",
      "updateTime": "2019-10-11T21:33:02.000+0000"
    },
    {
      "id": "slide-100003",
      "imageUrl": "http://www.life-runner.com/2019/11/CpoxxF0ZmHuAPlXvAAFe-H5_-Nw961.png",
      "backgroundColor": "#ff9801",
      "productId": "y200008",
      "catId": "",
      "type": 1,
      "sort": 1,
      "isShow": 1,
      "createTime": "2019-10-11T21:33:01.000+0000",
      "updateTime": "2019-10-11T21:33:02.000+0000"
    }
  ],
  "ok": true
}

福利講解

在我們的實現代碼中,有心的同學可以看到,我使用了3種不同的Bean注入方式:

  • 屬性注入
    @Autowired
    private ISlideAdService slideAdService;
  • 構造函數注入
    // 注入mapper
    private final SlideAdsMapper slideAdsMapper;

    @Autowired
    public SlideAdServiceImpl(SlideAdsMapper slideAdsMapper) {
        this.slideAdsMapper = slideAdsMapper;
    }
  • Lombok插件注入(本質也是構造器注入,代碼會動態生成。)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ProductServiceImpl implements IProductService {
    // RequiredArgsConstructor 構造器注入
    private final ProductCustomMapper productCustomMapper;
    private final ProductsMapper productsMapper;
    ...
}

那麼,這幾種注入都有什麼區別呢?首先我們下了解一下Spring的注入是干什麼的?

Spring提出了依賴注入的思想,即依賴類不由程序員實例化,而是通過Spring容器幫我們new指定實例並且將實例注入到需要該對象的類中。
依賴注入的另一種說法是”控制反轉”。通俗的理解是:平常我們new一個實例,這個實例的控制權是我們程序員, 而控制反轉是指new實例工作不由我們程序員來做而是交給Spring容器來做。

在傳統的SpringMVC中,大家使用的都是XML注入,比如:

<!--配置bean,配置后該類由spring管理--> 
<bean name="CategorySericeImpl" class="com.liferunner.service.impl.CategorySericeImpl"> 
<!--注入配置當前類中相應的屬性--> 
<property name="categoryMapper" ref="categoryMapper"></property> 
</bean> 
<bean name="categoryMapper" class="com.liferunner.mapper.CategoryMapper"></bean>

注入之後,使用@Autowired,我們可以很方便的自動從IOC容器中查找屬性,並返回。

@Autowired的原理
在啟動spring IoC時,容器自動裝載了一個AutowiredAnnotationBeanPostProcessor後置處理器,當容器掃描到@Autowied、@Resource或@Inject時,就會在IoC容器自動查找需要的bean,並裝配給該對象的屬性。
注意事項:
在使用@Autowired時,首先在容器中查詢對應類型的bean
如果查詢結果剛好為一個,就將該bean裝配給@Autowired指定的數據
如果查詢的結果不止一個,那麼@Autowired會根據名稱來查找。
如果查詢的結果為空,那麼會拋出異常。解決方法時,使用required=false

上述三種註解方式,其實本質上還是注入的2種:set屬性注入 & 構造器注入,使用方式都可以,根據個人喜好來用,本人喜歡使用lombok插件注入是因為,它將代碼整合在一起,更加符合我們Spring自動注入的規範。

源碼下載

下節預告

下一節我們將繼續開發我們電商的核心部分-商品列表和詳情展示,在過程中使用到的任何開發組件,我都會通過專門的一節來進行介紹的,兄弟們末慌!

gogogo!

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

結合參數接收響應轉換原理講解SpringBoot常用註解

一、常用註解回顧

1.1 @RequestBody與@ResponseBody


//注意並不要求@RequestBody與@ResponseBody成對使用。
public @ResponseBody  AjaxResponse saveArticle(@RequestBody ArticleVO article)

如上代碼所示:

  • @RequestBody修飾請求參數,註解用於接收HTTP的body,默認是使用JSON的格式
  • @ResponseBody修飾返回值,註解用於在HTTP的body中攜帶響應數據,默認是使用JSON的格式。如果不加該註解,spring響應字符串類型,是跳轉到模板頁面或jsp頁面的開發模式。說白了:加上這個註解你開發的是一個數據接口,不加這個註解你開發的是一個頁面跳轉控制器。

那麼我們有一個問題:如果我們想接收或XML數據該怎麼辦?我們想響應excel的數據格式該怎麼辦?我們後文來回答這個問題。

1.2. @RequestMapping註解

@RequestMapping註解是所有常用註解中,最有看點的一個註解,用於標註HTTP服務端點。它的很多屬性對於豐富我們的應用開發方式方法,都有很重要的作用。如:

  • value: 應用請求端點,最核心的屬性,用於標誌請求處理方法的唯一性;
  • method: HTTP協議的method類型, 如:GET、POST、PUT、DELETE等;
  • consumes: HTTP協議請求內容的數據類型(Content-Type),例如application/json, text/html;
  • produces: HTTP協議響應內容的數據類型。下文會詳細講解。
  • params: HTTP請求中必須包含某些參數值的時候,才允許被註解標註的方法處理請求。
  • headers: HTTP請求中必須包含某些指定的header值,才允許被註解標註的方法處理請求。

@RequestMapping(value = "/article", method = POST)
@PostMapping(value = "/article")

上面代碼中兩種寫法起到的是一樣的效果,也就是PostMapping等同於@RequestMapping的method等於POST。同理:@GetMapping、@PutMapping、@DeleteMapping也都是簡寫的方式。

1.3. @RestController與@Controller

@Controller註解是開發中最常使用的註解,它的作用有兩層含義:

  • 一是告訴Spring,被該註解標註的類是一個Spring的Bean,需要被注入到Spring的上下文環境中。
  • 二是該類裏面所有被RequestMapping標註的註解都是HTTP服務端點。

@RestController相當於 @Controller和@ResponseBody結合。它有兩層含義:

  • 一是作為Controller的作用,將控制器類注入到Spring上下文環境,該類RequestMapping標註方法為HTTP服務端點。
  • 二是作為ResponseBody的作用,請求響應默認使用的序列化方式是JSON,而不是跳轉到jsp或模板頁面。

1.4. @PathVariable 與@RequestParam

PathVariable用於URI上的{參數},如下方法用於刪除一篇文章,其中id為文章id。如:我們的請求URL為“/article/1”,那麼將匹配DeleteMapping並且PathVariable接收參數id=1。而RequestParam用於接收普通表單方式或者ajax模擬表單提交的參數數據。

@DeleteMapping("/article/{id}")
public @ResponseBody AjaxResponse deleteArticle(@PathVariable Long id) {

@PostMapping("/article")
public @ResponseBody AjaxResponse deleteArticle(@RequestParam Long id) {

二、接收複雜嵌套對象參數

有一些朋友可能還無法理解RequestBody註解存在的真正意義,表單數據提交用RequestParam就好了,為什麼還要搞出來一個RequestBody註解呢?RequestBody註解的真正意義在於能夠使用對象或者嵌套對象接收前端數據。

仔細看上面的代碼,是一個paramData對象裡面包含了一個bestFriend對象。這種數據結構使用RequestParam就無法接收了,RequestParam只能接收平面的、一對一的參數。像上文中這種數據結構的參數,就需要我們在java服務端定義兩個類,一個類是ParamData,一個類是BestFriend.

public class ParamData {
    private String name;
    private int id;
    private String phone;
    private BestFriend bestFriend;
    
    public static class BestFriend {
        private String address;
        private String sex;
    }
}
  • 注意上面代碼中省略了GET、SET方法等必要的java plain model元素。
  • 注意成員變量名稱一定要和JSON屬性名稱對應上。
  • 注意接收不同類型的參數,使用不同的成員變量類型

完成以上動作,我們就可以使用@RequestBody ParamData paramData,一次性的接收以上所有的複雜嵌套對象參數了,參數對象的所有屬性都將被賦值。

三、Http數據轉換的原理

大家現在使用JSON都比較普遍了,其方便易用、表達能力強,是絕大部分數據接口式應用的首選。那麼如何響應其他的類型的數據?其中的判別原理又是什麼?下面就來給大家介紹一下:

  • 當一個HTTP請求到達時是一個InputStream,通過HttpMessageConverter轉換為java對象,從而進行參數接收。
  • 當對一個HTTP請求進行響應時,我們首先輸出的是一個java對象,然後由HttpMessageConverter轉換為OutputStream輸出。

當我們在Spring Boot應用中集成了jackson的類庫之後,如下的一些HttpMessageConverter將會被加載。

實現類 功能說明
StringHttpMessageConverter 將請求信息轉為字符串
FormHttpMessageConverter 將表單數據讀取到MultiValueMap中
XmlAwareFormHttpMessageConverter 擴展與FormHttpMessageConverter,如果部分表單屬性是XML數據,可用該轉換器進行讀取
ResourceHttpMessageConverter 讀寫org.springframework.core.io.Resource對象
BufferedImageHttpMessageConverter 讀寫BufferedImage對象
ByteArrayHttpMessageConverter 讀寫二進制數據
SourceHttpMessageConverter 讀寫java.xml.transform.Source類型的對象
MarshallingHttpMessageConverter 通過Spring的org.springframework,xml.Marshaller和Unmarshaller讀寫XML消息
Jaxb2RootElementHttpMessageConverter 通過JAXB2讀寫XML消息,將請求消息轉換為標註的XmlRootElement和XmlType連接的類中
MappingJacksonHttpMessageConverter 利用Jackson開源包的ObjectMapper讀寫JSON數據
RssChannelHttpMessageConverter 讀寫RSS種子消息
AtomFeedHttpMessageConverter 和RssChannelHttpMessageConverter能夠讀寫RSS種子消息

根據HTTP協議的Accept和Content-Type屬性,以及參數數據類型來判別使用哪一種HttpMessageConverter。當使用RequestBody或ResponseBody時,再結合前端發送的Accept數據類型,會自動判定優先使用MappingJacksonHttpMessageConverter作為數據轉換器。但是,不僅JSON可以表達對象數據類型,XML也可以。如果我們希望使用XML格式該怎麼告知Spring呢,那就要使用到produces屬性了。

@GetMapping(value ="/demo",produces = MediaType.APPLICATION_XML_VALUE)

這裏我們明確的告知了返回的數據類型是xml,就會使用Jaxb2RootElementHttpMessageConverter作為默認的數據轉換器。當然實現XML數據響應比JSON還會更複雜一些,還需要結合@XmlRootElement、@XmlElement等註解實體類來使用。同理consumes屬性你是不是也會用了呢。

四、自定義HttpMessageConverter

其實絕大多數的數據格式都不需要我們自定義HttpMessageConverter,都有第三方類庫可以幫助我們實現(包括下文代碼中的Excel格式)。但有的時候,有些數據的輸出格式並沒有類似於Jackson這種類庫幫助我們處理,需要我們自定義數據格式。該怎麼做?下面代碼只是幫助我們理解的一個例子,不要用於生產:

@Service
public class TeamToXlsConverter extends AbstractHttpMessageConverter<Team> {

    private static final MediaType EXCEL_TYPE = MediaType.valueOf("application/vnd.ms-excel");

    TeamToXlsConverter() {
        super(EXCEL_TYPE);
    }

    @Override
    protected Team readInternal(final Class<? extends Team> clazz, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected boolean supports(final Class<?> clazz) {
        return (Team.class == clazz);
    }

    @Override
    protected void writeInternal(final Team team, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        try (final Workbook workbook = new HSSFWorkbook()) {
            final Sheet sheet = workbook.createSheet();
            int rowNo = 0;
            for (final TeamMember member : team.getMembers()) {
                final Row row = sheet.createRow(rowNo++);
                row.createCell(0)
                   .setCellValue(member.getName());
            }
            workbook.write(outputMessage.getBody());
        }
    }
}
  • 實現AbstractHttpMessageConverter接口
  • 指定該轉換器是針對哪種數據格式的?如上文代碼中的”application/vnd.ms-excel”
  • 指定該轉換器針對那些對象數據類型?如上文代碼中的supports函數
  • 使用writeInternal對數據進行輸出處理,上例中是輸出為Excel格式。

    期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

第 1 份工作,我只幹了 2 周就被辭退了 | 十年系列

1.寫在前面

我將用系列文章,回顧十年程序生涯,一方面是對職場生涯的階段性總結,另一方面希望這些經歷,對讀者往後職場生涯有所啟發。

一隻站在樹上的鳥兒,從來不會害怕樹枝斷裂,因為它相信的不是樹枝,而是它自己的翅膀。

十年系列文章:

2.我的第一份工作

畢業后很多年,我都不好意思和別人提起這件事,第一份工作只上了 2 周班就被辭退了!這是我第一次被人主動辭退,當然也是我職場上的最後一次。

今天回想起來,如果當初沒有被這家公司辭退,我還能成為今天的我嗎?

人生有時候就是這麼神奇。

到底是怎麼回事,我給大家詳細講講。

上篇文章
中講到,從培訓機構培訓完后我找到了一份滿意的工作,也是我從校園出來的第一份工作,我們一個培訓班進了5個人,所以大家租房上班都在一起,每天高高興興的上班下班,我對工作也充滿了信心和期待。

這個香港來的電信公司還不錯,五險一金和其它福利在西安同等公司裏面算好的,最最重要的是這家公司還會給每個入職的同事做一個月的免費培訓,主要的培訓內容就是 Oracle 和 Linux 相關知識。

3.入職培訓

我們那一期總共有18個人入職一起培訓,培訓的地點就在西安軟件園的地下一層,每次早上都是老師先在前面給大家培訓 Sql 的使用,然後再給大家出幾道練習題實踐。

那個時候剛從學校的氛圍裏面出來,完全不知道職場是怎麼回事。我旁邊坐的小夥伴也是和我一樣從培訓班出來的,他就非常的聰明,每次實踐自己做出來之後,就會主動的喊老師過來看一下他的運行結果。

反觀我這邊,有些簡單的或者複雜的實踐題,有的我做出來了有的沒有做出來,做出來之後也不好意思找老師去看,就默默的坐在工位上,整個人感覺都是木木的,缺乏表現。

前排經常有一些同事,只是解出了其中的一些步驟,都會主動的喊老師過來檢查,這樣在老師的幫助下也很快的完成了,多次交流和老師也熟悉了一點。

我自己有時候搞不定,請求隔壁小夥伴的時候,他很聰明直接把電腦屏幕轉個90度,讓老師看到,感覺我就像是抄他的一樣,其實也確實是抄。

而有時候我解出來程序,他沒有做出來的時候,就參考我的內容完成,然後再举手讓老師看他解決了出來,我坐在裏面也沒有給老師反饋,老師可能也覺得我沒有做出來,就這樣過去了。

4.被辭退

兩個星期之後,老師宣布要從18個人裏面淘汰掉 2 個同事,其中就有我。

現在回想起來如果我是那位培訓的老師,我可能也會選擇淘汰掉當時的我,入職之後完全沒有轉變過來學生的身份,仍然像上大學一樣,坐在最後一排默默的干自己的事情。

現在反思當時的自己,應該有以下幾個問題:

  • ① 不积極不主動,這不是學校,這是人家給你錢上班,需要給公司解決問題的。

  • ② 缺乏即時反饋,當你的工作做完之後就應該需要讓你的領導知道。

  • ③ 工作狀態不對,沒有盡全力去學習工作,試用期正是考驗員工的時候呀。

  • ④ 技術能力不行,剛從培訓班出來技術很菜,自己卻不知道。

  • ⑤ 心態轉變太差,還是學生時代的心態,沒有融入到職場。

  • ⑥ 精神面貌不對,在職場不說穿得多好多正式,至少要精神一點,自己當時穿衣確實不像上班的。

基於以上各種我認為的原因,我被開除了!

那次培訓18個人,2個人被辭退,當我把這個信息告訴我姐的時候,我姐說關鍵人家就只淘汰了2個人,其中就有你,我無言以對。

5.又開始重新找工作

從培訓班畢業後身上就沒有多少錢了,入職第一家公司的時候,培訓班的幾個同學都說第一筆工資怎麼怎麼花,有的說要請一家人吃飯,有的說要給女朋友送個禮物,有的說要犒勞一下自己。

我當時什麼話也沒說,因為我的錢誰也給不了,我要留着吃飯交房租。

被辭退後,我就開始一個人在網上瘋狂的投起簡歷來,當時對 IT 行業理解也不深,並不知道什麼是互聯網公司,什麼是傳統軟件公司,什麼是外包公司,但凡只要招聘 Java 工程師的我都投遞了簡歷。

就這樣陸陸續續的收到了幾家公司的面試邀請,我這裏印象比較深刻的有三家公司,因為就這三家公司給了我 Offer。

第一家公司是個小老闆,給 2000 的工資但不給交社保,公司一共也就十幾個人,其中只有5、6個程序員,老闆本身也是程序員出身,主要業務是給別的公司做項目。

第二家公司是深圳易思博,當時華為、中興紛紛在西安創建了研發基地,國內比較著名的外包公司也都紛紛跟了過來,易思博也是當時的外包公司之一。

當時大部分外包都是人力外包,就是別的公司招聘你,你直接去甲方公司上班,但是是乙方公司給你發工資的,當時去華為做人力外包也是需要華為的人來面試的。

第三家公司是一家電信公司,具體是負責中國電信旗下的一個項目,面試官年紀比較大當時和我聊得很不錯,現在還記得他當時主要問我關於 IO 的幾個問題,有些沒有回答好,在他的提示下也都給出了答案。

在這家公司面試的時候非常舒服,就像和一位老大哥在探討技術一樣,就算回答不出來的問題,他也會幫忙給與解釋,讓我學習到了很多。

6.新的選擇

用了差不多兩周的時間,我又重新收穫了 3 份 Offer,其實畢業以來我的面試通過率一般都挺高的,我都覺得自己挺擅長面試的,工資從 1800-2100 差別不是很大。

當時給我姐打了很多電話,讓她幫忙給我參考,但她其實也不太懂這個行業,只是說應該大公司的更保險一點吧。

最終我糾結了3天,選擇了其中一家工資最高的公司,沒想到這又跳入了另外一個坑裡面。具體是掉進什麼樣的坑裡面,下篇再和大家細聊。

7.畢業生如何選擇 Offer

還是想和剛畢業的朋友聊兩句,第一份工作對我們的選擇來講很重要,很有可能決定了你未來職場的發展方向,所以職場選擇的時候我給大家提供幾個建議:

  • 第一:優先選擇大的互聯網公司,也就是我們所說的大廠,大廠有比較完整的培訓體系和成長環境,可以學到中國甚至世界級的系統設計;另外工作兩三年後有大廠經歷,再次出來面試會有加分。

  • 第二:選有潛力的獨角獸公司,比如當初的頭條或者美團都是非常好的選擇,這類公司的發展非常迅速,個人可以跟隨着公司的快速發展一起成長,早期還可以拿到比較多的期權,有可能選擇好一家公司就可以實現財富自由。

  • 第三:選擇傳統軟件公司,比如:神州數碼、中興、華為、用友軟件等公司,傳統軟件公司的技術棧往往相對保守,這是公司的產品性質決定,同時發展速度有限,這些年有些傳統軟件公司也迭代很快,這裏只說大部分傳統軟件公司。華為在這些傳統軟件公司中比較特殊一點,我有幾個朋友也在華為呆了很多年,華為確實給的錢很多,同時工作壓力也非常大,所以需要個人來結合自己情況來選擇。

  • 第四:謹慎選擇小的創業公司,創業公司的死亡率非常高,很多公司到後面都出現了拖欠工資等現象,公司情況完全依賴於老闆的情懷。

最後希望大家避免我的經歷,积極、認真、主動的對待人生第一份工作,迎接好走向社會的第一步,希望讀者們都能找到自己的理想生活。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

小白學 Python(24):Excel 基礎操作(下)

人生苦短,我選Python

前文傳送門

在這裏首先恭喜各位看到本篇連載的同學,本篇連載為 《小白學 Python 基礎系列》 最後一篇,恭喜各位在學習 Python 的道路上邁出了堅實的一大步。

寫入 Excel

首先當然是安裝第三方模塊:

pip install openpyxl

首先我們需要先創建一個 WorkBook :

import xlsxwriter

workbook = xlsxwriter.Workbook('demo.xlsx')

在所有操作之前,需要記得先導入我們剛才安裝的 xlsxwriter 的模塊。

接下來,我們創建一個 Sheet :

sheet1 = workbook.add_worksheet('test_sheet')

創建完成后,需要關閉 workbook ,這一步會將我們剛才創建的 workbook 進行保存。

workbook.close()

好了,我們已經創建好了一個 excel ,操作結束,下課。

老師,你回來,這就完了?

我們接着介紹如何將數據寫入至 Excel 中。

首先我們可以先設置一些的單元格的格式:

workfomat = workbook.add_format()
# 字體加粗
workfomat.set_bold(True)
# 單元格邊框寬度
workfomat.set_border(1)
# 對齊方式
workfomat.set_align('left')
# 格式化數據格式為小數點后兩位
workfomat.set_num_format('0.00')

然後我們將內容寫入,具體內容小編懶得想了,直接複製上一篇文章中的內容:

heads = ['', '語文', '數學', '英語']
datas = [
    ['小明', 76, 85, 95],
    ['小紅', 85, 58, 92],
    ['小王', 98, 96, 91]
]

sheet1.write_row('A1', heads, workfomat)

sheet1.write_row('A2', datas[0], workfomat)
sheet1.write_row('A3', datas[1], workfomat)
sheet1.write_row('A4', datas[2], workfomat)

然後執行程序,我們來看下最終輸出的結果:

除了可以這樣輸出以外,我們還可以指定輸出的單元格格式:

我們列舉一個比較複雜的輸出日期類型:

fomat1 = workbook.add_format({'num_format': 'yy/mm/dd/ hh:mm:ss'})

sheet1.write_datetime('E5', datetime.datetime(2019, 11, 9, 22, 44, 26), fomat1)

注意: 上面的格式化一定要加,否則在 Excel 中显示出來的只會是一個時間戳。

其他的輸出類型小編這裏就不一一舉例了,下面列出一些常用的:

# 字符串類型
sheet1.write_string()
# 数字型
sheet1.wirte_number()
# 空類型
sheet1.write_blank()
# 公式
sheet1.write_formula()
# 布爾型
sheet1.write_boolean()
# 超鏈接
sheet1.write_url()

我們還可以在 Excel 中插入圖片,樣例如下:

sheet1.insert_image('I6', 'wx.jpg')

語法如下:

insert_image(row, col, image[, options])

row:行坐標,起始索引值為0;
col:列坐標,起始索引值為0;
image:string類型,是圖片路徑;
options:dict類型,是可選參數,用於指定圖片位置,如URL等信息;

我們還可以在 Excel 中繪圖,支持包括面積、條形圖、柱狀圖、折線圖、散點圖等。

圖表對象是通過 Workbook add_chart() 方法創建的,其中指定了圖表類型:

chart = workbook.add_chart({'type': 'column'})

常見的圖表樣式如下:

area:面積樣式的圖表
bar:條形圖
column:柱狀圖
line:線條樣式的圖表
pie:餅形圖
scatter:散點圖
stock:股票樣式的圖表
radar:雷達樣式的圖表

然後使用 insert_chart() Worksheet方法將其作為嵌入的圖表插入到工作表中:

sheet1.insert_chart('A7', chart)

完整示例如下:

chart = workbook.add_chart({'type': 'column'})

chart.add_series({'values': '=test_sheet!$B$2:$B$4'})
chart.add_series({'values': '=test_sheet!$C$2:$C$4'})
chart.add_series({'values': '=test_sheet!$D$2:$D$4'})

sheet1.insert_chart('A7', chart)

結果如下:

一些常用的簡單的操作就介紹到這裏,想了解更多的同學可以訪問官方文檔,鏈接小編已經找出來了: 。

示例代碼

本系列的所有代碼小編都會放在代碼管理倉庫 Github 和 Gitee 上,方便大家取用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

Python 命令行之旅:深入 click 之選項篇

作者:HelloGitHub-Prodesire

HelloGitHub 的《講解開源項目》系列,項目地址:https://github.com/HelloGitHub-Team/Article

一、前言

在上一篇文章中,我們介紹了 click 中的“參數”,本文將繼續深入了解 click,着重講解它的“選項”。

本系列文章默認使用 Python 3 作為解釋器進行講解。
若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~

二、選項

通過 click.option 可以給命令增加選項,並通過配置函數的參數來配置不同功能的選項。

2.1 給選項命名

click.option 中的命令規則可參考。它接受的前兩個參數為長、短選項(順序隨意),其中:

  • 長選項以 “–” 開頭,比如 “–string-to-echo”
  • 短選項以 “-” 開頭,比如 “-s”

第三個參數為選項參數的名稱,如果不指定,將會使用長選項的下劃線形式名稱:

@click.command()
@click.option('-s', '--string-to-echo')
def echo(string_to_echo):
    click.echo(string_to_echo)

显示指定為 string

@click.command()
@click.option('-s', '--string-to-echo', 'string')
def echo(string):
    click.echo(string)

2.2 基本值選項

值選項是非常常用的選項,它接受一個值。如果在命令行中提供了值選項,則需要提供對應的值;反之則使用默認值。若沒在 click.option 中指定默認值,則默認值為 None,且該選項的類型為 ;反之,則選項類型為默認值的類型。

比如,提供默認值為 1,則選項類型為 :

@click.command()
@click.option('--n', default=1)
def dots(n):
    click.echo('.' * n)

如果要求選項為必填,則可指定 click.optionrequired=True

@click.command()
@click.option('--n', required=True, type=int)
def dots(n):
    click.echo('.' * n)

如果選項名稱和 Python 中的關鍵字衝突,則可以顯式的指定選項名稱。比如將 --from 的名稱設置為 from_

@click.command()
@click.option('--from', '-f', 'from_')
@click.option('--to', '-t')
def reserved_param_name(from_, to):
    click.echo(f'from {from_} to {to}')

如果要在幫助中顯式默認值,則可指定 click.optionshow_default=True

@click.command()
@click.option('--n', default=1, show_default=True)
def dots(n):
    click.echo('.' * n)

在命令行中調用則有:

$ dots --help
Usage: dots [OPTIONS]

Options:
  --n INTEGER  [default: 1]
  --help       Show this message and exit.

2.3 多值選項

有時,我們會希望命令行中一個選項能接收多個值,通過指定 click.option 中的 nargs 參數(必須是大於等於 0)。這樣,接收的多值選項就會變成一個元組。

比如,在下面的示例中,當通過 --pos 指定多個值時,pos 變量就是一個元組,裏面的每個元素是一個 float

@click.command()
@click.option('--pos', nargs=2, type=float)
def findme(pos):
    click.echo(pos)

在命令行中調用則有:

$ findme --pos 2.0 3.0
(1.0, 2.0)

有時,通過同一選項指定的多個值得類型可能不同,這個時候可以指定 click.option 中的 type=(類型1, 類型2, ...) 來實現。而由於元組的長度同時表示了值的數量,所以就無須指定 nargs 參數。

@click.command()
@click.option('--item', type=(str, int))
def putitem(item):
    click.echo('name=%s id=%d' % item)

在命令行中調用則有:

$ putitem --item peter 1338
name=peter id=1338

2.4 多選項

不同於多值選項是通過一個選項指定多個值,多選項則是使用多個相同選項分別指定值,通過 click.option 中的 multiple=True 來實現。

當我們定義如下多選項:

@click.command()
@click.option('--message', '-m', multiple=True)
def commit(message):
    click.echo('\n'.join(message))

便可以指定任意數量個選項來指定值,獲取到的 message 是一個元組:

$ commit -m foo -m bar --message baz
foo
bar
baz

2.5 計值選項

有時我們可能需要獲得選項的數量,那麼可以指定 click.option 中的 count=True 來實現。

最常見的使用場景就是指定多個 --verbose-v 選項來表示輸出內容的詳細程度。

@click.command()
@click.option('-v', '--verbose', count=True)
def log(verbose):
    click.echo(f'Verbosity: {verbose}')

在命令行中調用則有:

$ log -vvv
Verbosity: 3

通過上面的例子,verbose 就是数字,表示 -v 選項的數量,由此可以進一步使用該值來控制日誌的詳細程度。

2.6 布爾選項

布爾選項用來表示真或假,它有多種實現方式:

  • 通過 click.optionis_flag=True 參數來實現:
import sys

@click.command()
@click.option('--shout', is_flag=True)
def info(shout):
    rv = sys.platform
    if shout:
        rv = rv.upper() + '!!!!111'
    click.echo(rv)

在命令行中調用則有:

$ info --shout
LINUX!!!!111
  • 通過在 click.option 的選項定義中使用 / 分隔表示真假兩個選項來實現:
import sys

@click.command()
@click.option('--shout/--no-shout', default=False)
def info(shout):
    rv = sys.platform
    if shout:
        rv = rv.upper() + '!!!!111'
    click.echo(rv)

在命令行中調用則有:

$ info --shout
LINUX!!!!111
$ info --no-shout
linux

在 Windows 中,一個選項可以以 / 開頭,這樣就會真假選項的分隔符衝突了,這個時候可以使用 ; 進行分隔:

@click.command()
@click.option('/debug;/no-debug')
def log(debug):
    click.echo(f'debug={debug}')

if __name__ == '__main__':
    log()

在 cmd 中調用則有:

> log /debug
debug=True

2.7 特性切換選項

所謂特性切換就是切換同一個操作對象的不同特性,比如指定 --upper 就讓輸出大寫,指定 --lower 就讓輸出小寫。這麼來看,布爾值其實是特性切換的一個特例。

要實現特性切換選項,需要讓多個選項都有相同的參數名稱,並且定義它們的標記值 flag_value

import sys

@click.command()
@click.option('--upper', 'transformation', flag_value='upper',
              default=True)
@click.option('--lower', 'transformation', flag_value='lower')
def info(transformation):
    click.echo(getattr(sys.platform, transformation)())

在命令行中調用則有:

$ info --upper
LINUX
$ info --lower
linux
$ info
LINUX

在上面的示例中,--upper--lower 都有相同的參數值 transformation

  • 當指定 --upper 時,transformation 就是 --upper 選項的標記值 upper
  • 當指定 --lower 時,transformation 就是 --lower 選項的標記值 lower

進而就可以做進一步的業務邏輯處理。

2.8 選擇項選項

選擇項選項 和 上篇文章中介紹的 選擇項參數 類似,只不過是限定選項內容,依舊是通過 type=click.Choice 實現。此外,case_sensitive=False 還可以忽略選項內容的大小寫。

@click.command()
@click.option('--hash-type',
              type=click.Choice(['MD5', 'SHA1'], case_sensitive=False))
def digest(hash_type):
    click.echo(hash_type)

在命令行中調用則有:

$ digest --hash-type=MD5
MD5

$ digest --hash-type=md5
MD5

$ digest --hash-type=foo
Usage: digest [OPTIONS]
Try "digest --help" for help.

Error: Invalid value for "--hash-type": invalid choice: foo. (choose from MD5, SHA1)

$ digest --help
Usage: digest [OPTIONS]

Options:
  --hash-type [MD5|SHA1]
  --help                  Show this message and exit.

2.9 提示選項

顧名思義,當提供了選項卻沒有提供對應的值時,會提示用戶輸入值。這種交互式的方式會讓命令行變得更加友好。通過指定 click.option 中的 prompt 可以實現。

  • prompt=True 時,提示內容為選項的參數名稱
@click.command()
@click.option('--name', prompt=True)
def hello(name):
    click.echo(f'Hello {name}!')

在命令行調用則有:

$ hello --name=John
Hello John!
$ hello
Name: John
Hello John!
  • prompt='Your name please' 時,提示內容為指定內容
@click.command()
@click.option('--name', prompt='Your name please')
def hello(name):
    click.echo(f'Hello {name}!')

在命令行中調用則有:

$ hello
Your name please: John
Hello John!

基於提示選項,我們還可以指定 hide_input=True 來隱藏輸入,confirmation_prompt=True 來讓用戶進行二次輸入,這非常適合輸入密碼的場景。

@click.command()
@click.option('--password', prompt=True, hide_input=True,
              confirmation_prompt=True)
def encrypt(password):
    click.echo(f'Encrypting password to {password.encode("rot13")}')

當然,也可以直接使用 click.password_option

@click.command()
@click.password_option()
def encrypt(password):
    click.echo(f'Encrypting password to {password.encode("rot13")}')

我們還可以給提示選項設置默認值,通過 default 參數進行設置,如果被設置為函數,則可以實現動態默認值。

@click.command()
@click.option('--username', prompt=True,
              default=lambda: os.environ.get('USER', ''))
def hello(username):
    print("Hello,", username)

詳情請閱讀 。

2.10 範圍選項

如果希望選項的值在某個範圍內,就可以使用範圍選項,通過指定 type=click.IntRange 來實現。它有兩種模式:

  • 默認模式(非強制模式),如果值不在區間範圍內將會引發一個錯誤。如 type=click.IntRange(0, 10) 表示範圍是 [0, 10],超過該範圍報錯
  • 強制模式,如果值不在區間範圍內,將會強制選取一個區間臨近值。如 click.IntRange(0, None, clamp=True) 表示範圍是 [0, +∞),小於 0 則取 0,大於 20 則取 20。其中 None 表示沒有限制
@click.command()
@click.option('--count', type=click.IntRange(0, None, clamp=True))
@click.option('--digit', type=click.IntRange(0, 10))
def repeat(count, digit):
    click.echo(str(digit) * count)

if __name__ == '__main__':
    repeat()

在命令行中調用則有:

$ repeat --count=1000 --digit=5
55555555555555555555
$ repeat --count=1000 --digit=12
Usage: repeat [OPTIONS]

Error: Invalid value for "--digit": 12 is not in the valid range of 0 to 10.

2.11 回調和優先

回調
通過 click.option 中的 callback 可以指定選項的回調,它會在該選項被解析后調用。回調函數的簽名如下:

def callback(ctx, param, value):
    pass

其中:

  • ctx 是命令的上下文
  • param 為選項變量
  • value 為選項的值

使用回調函數可以完成額外的參數校驗邏輯。比如,通過 –rolls 的選項來指定搖骰子的方式,內容為“{N}d{M}”,表示 M 面的骰子搖 N 次,N 和 M 都是数字。在真正的處理 rolls 前,我們需要通過回調函數來校驗它的格式:

def validate_rolls(ctx, param, value):
    try:
        rolls, dice = map(int, value.split('d', 2))
        return (dice, rolls)
    except ValueError:
        raise click.BadParameter('rolls need to be in format NdM')

@click.command()
@click.option('--rolls', callback=validate_rolls, default='1d6')
def roll(rolls):
    click.echo('Rolling a %d-sided dice %d time(s)' % rolls)

這樣,當我們輸入錯誤格式時,變會校驗不通過:

$ roll --rolls=42
Usage: roll [OPTIONS]

Error: Invalid value for "--rolls": rolls need to be in format NdM

輸入正確格式時,則正常輸出信息:

$ roll --rolls=2d12
Rolling a 12-sided dice 2 time(s)

優先
通過 click.option 中的 is_eager 可以讓該選項成為優先選項,這意味着它會先於所有選項處理。

利用回調和優先選項,我們就可以很好地實現 --version 選項。不論命令行中寫了多少選項和參數,只要包含了 --version,我們就希望它打印版本就退出,而不執行其他選項的邏輯,那麼就需要讓它成為優先選項,並且在回調函數中打印版本。

此外,在 click 中每個選項都對應到命令處理函數的同名參數,如果不想把該選項傳遞到處理函數中,則需要指定 expose_value=True,於是有:

def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo('Version 1.0')
    ctx.exit()

@click.command()
@click.option('--version', is_flag=True, callback=print_version,
              expose_value=False, is_eager=True)
def hello():
    click.echo('Hello World!')

當然 click 提供了便捷的 click.version_option 來實現 --version

@click.command()
@click.version_option(version='0.1.0')
def hello():
    pass

2.12 Yes 選項

基於前面的學習,我們可以實現 Yes 選項,也就是對於某些操作,不提供 --yes 則進行二次確認,提供了則直接操作:

def abort_if_false(ctx, param, value):
    if not value:
        ctx.abort()

@click.command()
@click.option('--yes', is_flag=True, callback=abort_if_false,
              expose_value=False,
              prompt='Are you sure you want to drop the db?')
def dropdb():
    click.echo('Dropped all tables!')

當然 click 提供了便捷的 click.confirmation_option 來實現 Yes 選項:

@click.command()
@click.confirmation_option(prompt='Are you sure you want to drop the db?')
def dropdb():
    click.echo('Dropped all tables!')

在命令行中調用則有:

$ dropdb
Are you sure you want to drop the db? [y/N]: n
Aborted!
$ dropdb --yes
Dropped all tables!

2.11 其他增強功能

click 支持從環境中讀取選項的值,這是 argparse 所不支持的,可參閱官方文檔的 和 。

click 支持指定選項前綴,你可以不使用 - 作為選項前綴,還可使用 +/,當然在一般情況下並不建議這麼做。詳情參閱官方文檔的

三、總結

可以看出,click 對命令行選項的支持非常豐富和強大,除了支持 argarse 所支持的所有選項類型外,還提供了諸如 計值選項特性切換選項提示選項 等更豐富的選項類型。此外,還提供了從環境中讀變量等方便易用的增強功能。簡直就是開發命令行程序的利器。

在下篇文章中,我們着重介紹下 click 的命令和組,這可是實現它的重要特性(任意嵌套命令)的方式。

『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟着我們的文章,你會發現編程的樂趣、使用和發現參与開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

AspNetCore熟練應用CancellationToken,CTO會對你刮目相看

背景

  已經有很多文章記錄了 web程序中採用異步編程的優勢和.Net異步編程的用法, 異步編程雖然不能解決查詢數據庫的瓶頸, 但是利用線程切換,能最大限度的彈性利用工作線程, 提高了web服務的響應能力。

  【 9012年了,再不會異步編程你是真老了】

       本文要說的是利用異步編程中的取消機制緩解數據庫的查詢瓶頸開發者只需在 MVC/WebAPI查詢方法體內關注CancllationToken並適時取消異步任務, 這將大大提高應用的響應能力。

頭腦風暴

  想象你請求某網站頁面,該頁面正閃着菊花試圖努力綻放(正在加載),最終你忍不了:

① F5刷新

② 轉向其他頁面

③ 點擊瀏覽器“停止”按鈕 

對於可憐的服務器,用戶快速刷新5次,服務器將被迫接受 5倍的工作量,這是因為即使用戶刷新了瀏覽器(或點擊停止按鈕), 雖然取消了原始瀏覽器請求,但是Web服務器並不Care,仍然按部就班處理進入HTTP pipeline的請求(MVC/WebAPI 中默認行為)。其他②③場景類似。

在異步編程中能向任務發出Cancllation信號,停止web服務器一切後端查詢行為。在.NET中,這是使用CancellationToken完成的:

  • 取消令牌的實例傳遞到異步任務

  • 異步任務監視令牌,以查看請求是否已經被取消。

  • 如果請求取消,則應停止執行正在執行的操作。.NET中的大多數異步方法將具有接受取消令牌的重載。

本文所說的請求是,耗時長的服務端讀取查詢(返回數據但不修改數據的查詢)。取消已修改數據的請求對於用程序可能不是一個好的選擇:

    –  是否真的要因用戶導航到應用程序中的另一個頁面而取消保存?也許可以,但也可能不會。

   –  除了數據問題,這也不會提高性能,因為數據庫服務器將需要回滾該事務,這可能是一項昂貴的操作。

AspNetCore實踐

P1  監測CancellationToken令牌

  訪問 MyReallySlowReport頁面,等待5s,最終他們放棄了,去了其他頁面:

 所有正在進行的請求都將被取消。

MVC/WebAPI能接受到取消請求的信號。開發者只需要在Controller Action中添加CancellationToken參數,並在後續行為中監測該取消信號。

瀏覽器取消請求時,AspNetCore根據自動將HttpContext.RequestAborted這個token綁定到Action的CancellationToken 參數,CancellationTokenModelBinder將會在調用AddMvc()或services.AddMvcCore()時被注入。​

public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken)
{
    List<ReportItem> items;
    using (ApplicationDbContext context = new ApplicationDbContext())
    {
        items = await context.ReportItems.ToListAsync(cancellationToken);
    }
    return View(items);
}

很容易取消SQL的查詢行為,因為上述EF的調用api支持取消異步操作; 對於自定義的長耗時查詢行為,可以使用CancllationToken的原生觸發用法:

public async Task<ActionResult> MyReallySlowReport(CancellationToken cancellationToken)
{
    List<ReportItem> items;
    using (ApplicationDbContext context = new ApplicationDbContext())
    {
        items = await context.ReportItems.ToListAsync(cancellationToken);
    }

    foreach (var item in items)
    {
        cancellationToken.ThrowIfCancellationRequested();
            // slow non-cancellable work
            Thread.Sleep(1000);
    }
    return View(items);
}

 P2  處理取消異步操作向上拋出的異常 

 Web服務器觸發取消信號,一般會向上會拋出 OperationCanceledException 或者 TaskCancellationException,所以為了記錄這種非常規異常,建議採用獨立的ExceptionFilter記錄。

public class OperationCancelledExceptionFilter : ExceptionFilterAttribute
{
    private readonly ILogger _logger;

    public OperationCancelledExceptionFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<OperationCancelledExceptionFilter>();
    }
    public override void OnException(ExceptionContext context)
    {
        if(context.Exception is OperationCanceledException)
        {
            _logger.LogInformation("Request was cancelled");
            context.ExceptionHandled = true;
            context.Result = new StatusCodeResult(400);
        }
    }
}

P3  想要得到CTO的稱讚,可不是那麼簡單。

以上只是後端程序員利用取消機制緩解異步查詢瓶頸的後端操作,從web應用全流程角度思考,這個優化還能提升嗎?

> 以上是傳統的網頁請求場景,在取消請求時,瀏覽器幫助我們發起了Cancellation信號。 

> 想想日益常見的SPA程序(單頁面程序),絕大部分頁面請求都是Ajax請求,你點擊應用的另外一個“頁面(JS代碼維護頁面導航),瀏覽器不會自動取消請求。

所以在SPA應用中,要前端自行發出取消請求的信號

var xhr = $.get("/api/myslowreport", function(data){
  //show the data
});

//If the user navigates away from this page
xhr.abort() 

That‘s all ,前後端程序猿通力配合, 應用的吞吐量和響應能力極大提升, CTO要給各位加薪了。

 

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

南投搬家前需注意的眉眉角角,別等搬了再說!