主頁 > 前端設計 > Day400&401.商品服務 -谷粒商城

Day400&401.商品服務 -谷粒商城

2021-09-26 13:27:47 前端設計

商品服務

一、品牌管理

1、效果優化與快速顯示開關

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OpNSk0Kh-1632494568601)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924210757124.png)]

將逆向工程product得到的resources\src\views\modules\product檔案拷貝到achangmall/renren-fast-vue/src/views/modules/product目錄下,也就是下面的兩個檔案

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-r417UaT3-1632494568605)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924210824690.png)]

brand.vue : 顯示的表單
brand-add-or-update.vue:添加和更改功能

  • 但是顯示的頁面沒有新增和洗掉功能,這是因為權限控制的原因

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ExKvtYEF-1632494568618)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924210849186.png)]

<el-button v-if="isAuth('product:brand:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
<el-button v-if="isAuth('product:brand:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量洗掉</el-button>
  • 查看“isAuth”的定義位置:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6RLj7bxm-1632494568621)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924210930813.png)]

它是在“index.js”中定義,暫時將它設定為回傳值為true,即可顯示添加和洗掉功能, 再次重繪頁面能夠看到,按鈕已經出現了:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Txj7LtDV-1632494568624)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924210951855.png)]

進行添加 測驗成功

  • 進行修改 也會自動回顯 build/webpack.base.conf.js 中注釋掉createLintingRule()函式體,不進行lint語法檢

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oi1G9lOG-1632494568625)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924211413453.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6FQrTV5n-1632494568627)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924211512874.png)]

  • brand.vue
<template>
    <div class="mod-config">
        <el-form
                 :inline="true"
                 :model="dataForm"
                 @keyup.enter.native="getDataList()"
                 >
            <el-form-item>
                <el-input
                          v-model="dataForm.key"
                          placeholder="引數名"
                          clearable
                          ></el-input>
            </el-form-item>
            <el-form-item>
                <el-button @click="getDataList()">查詢</el-button>
                <el-button
                           v-if="isAuth('product:brand:save')"
                           type="primary"
                           @click="addOrUpdateHandle()"
                           >新增</el-button
                    >
                <el-button
                           v-if="isAuth('product:brand:delete')"
                           type="danger"
                           @click="deleteHandle()"
                           :disabled="dataListSelections.length <= 0"
                           >批量洗掉</el-button
                    >
            </el-form-item>
        </el-form>
        <el-table
                  :data="dataList"
                  border
                  v-loading="dataListLoading"
                  @selection-change="selectionChangeHandle"
                  style="width: 100%"
                  >
            <el-table-column
                             type="selection"
                             header-align="center"
                             align="center"
                             width="50"
                             >
            </el-table-column>
            <el-table-column
                             prop="brandId"
                             header-align="center"
                             align="center"
                             label="品牌id"
                             >
            </el-table-column>
            <el-table-column
                             prop="name"
                             header-align="center"
                             align="center"
                             label="品牌名"
                             >
            </el-table-column>
            <el-table-column
                             prop="logo"
                             header-align="center"
                             align="center"
                             label="品牌logo地址"
                             >
            </el-table-column>
            <el-table-column
                             prop="descript"
                             header-align="center"
                             align="center"
                             label="介紹"
                             >
            </el-table-column>
            <el-table-column
                             prop="showStatus"
                             header-align="center"
                             align="center"
                             label="顯示狀態"
                             >
                <template slot-scope="scope">
                    <el-switch
                               v-model="scope.row.showStatus"
                               active-color="#13ce66"
                               inactive-color="#ff4949"
                               :active-value="1"
                               :inactive-value="0"
                               @change="updateBrandStatus(scope.row)"
                               >
                    </el-switch>
                </template>
            </el-table-column>
            <el-table-column
                             prop="firstLetter"
                             header-align="center"
                             align="center"
                             label="檢索首字母"
                             >
            </el-table-column>
            <el-table-column
                             prop="sort"
                             header-align="center"
                             align="center"
                             label="排序"
                             >
            </el-table-column>
            <el-table-column
                             fixed="right"
                             header-align="center"
                             align="center"
                             width="150"
                             label="操作"
                             >
                <template slot-scope="scope">
                    <el-button
                               type="text"
                               size="small"
                               @click="addOrUpdateHandle(scope.row.brandId)"
                               >修改</el-button
                        >
                    <el-button
                               type="text"
                               size="small"
                               @click="deleteHandle(scope.row.brandId)"
                               >洗掉</el-button
                        >
                </template>
            </el-table-column>
        </el-table>
        <el-pagination
                       @size-change="sizeChangeHandle"
                       @current-change="currentChangeHandle"
                       :current-page="pageIndex"
                       :page-sizes="[10, 20, 50, 100]"
                       :page-size="pageSize"
                       :total="totalPage"
                       layout="total, sizes, prev, pager, next, jumper"
                       >
        </el-pagination>
        <!-- 彈窗, 新增 / 修改 -->
        <add-or-update
                       v-if="addOrUpdateVisible"
                       ref="addOrUpdate"
                       @refreshDataList="getDataList"
                       ></add-or-update>
    </div>
</template>

<script>
    import AddOrUpdate from "./brand-add-or-update";
    export default {
        data() {
            return {
                dataForm: {
                    key: "",
                },
                dataList: [],
                pageIndex: 1,
                pageSize: 10,
                totalPage: 0,
                dataListLoading: false,
                dataListSelections: [],
                addOrUpdateVisible: false,
            };
        },
        components: {
            AddOrUpdate,
        },
        activated() {
            this.getDataList();
        },
        methods: {
            updateBrandStatus(data) {
                console.log("最新資訊", data);
                let { brandId, showStatus } = data;
                this.$http({
                    url: this.$http.adornUrl("/product/brand/update"),
                    method: "post",
                    data: this.$http.adornData({ brandId, showStatus: showStatus }, false),
                }).then(({ data }) => {
                    this.$message({
                        type: "success",
                        message: "狀態更新成功",
                    });
                });
            },

            // 獲取資料串列
            getDataList() {
                this.dataListLoading = true;
                this.$http({
                    url: this.$http.adornUrl("/product/brand/list"),
                    method: "get",
                    params: this.$http.adornParams({
                        page: this.pageIndex,
                        limit: this.pageSize,
                        key: this.dataForm.key,
                    }),
                }).then(({ data }) => {
                    if (data && data.code === 0) {
                        this.dataList = data.page.list;
                        this.totalPage = data.page.totalCount;
                    } else {
                        this.dataList = [];
                        this.totalPage = 0;
                    }
                    this.dataListLoading = false;
                });
            },
            // 每頁數
            sizeChangeHandle(val) {
                this.pageSize = val;
                this.pageIndex = 1;
                this.getDataList();
            },
            // 當前頁
            currentChangeHandle(val) {
                this.pageIndex = val;
                this.getDataList();
            },
            // 多選
            selectionChangeHandle(val) {
                this.dataListSelections = val;
            },
            // 新增 / 修改
            addOrUpdateHandle(id) {
                this.addOrUpdateVisible = true;
                this.$nextTick(() => {
                    this.$refs.addOrUpdate.init(id);
                });
            },
            // 洗掉
            deleteHandle(id) {
                var ids = id
                ? [id]
                : this.dataListSelections.map((item) => {
                    return item.brandId;
                });
                this.$confirm(
                    `確定對[id=${ids.join(",")}]進行[${id ? "洗掉" : "批量洗掉"}]操作?`,
                    "提示",
                    {
                        confirmButtonText: "確定",
                        cancelButtonText: "取消",
                        type: "warning",
                    }
                ).then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/brand/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    }).then(({ data }) => {
                        if (data && data.code === 0) {
                            this.$message({
                                message: "操作成功",
                                type: "success",
                                duration: 1500,
                                onClose: () => {
                                    this.getDataList();
                                },
                            });
                        } else {
                            this.$message.error(data.msg);
                        }
                    });
                });
            },
        },
    };
</script>

  • brand-add-or-update.vue
<template>
    <el-dialog
               :title="!dataForm.brandId ? '新增' : '修改'"
               :close-on-click-modal="false"
               :visible.sync="visible"
               >
        <el-form
                 :model="dataForm"
                 :rules="dataRule"
                 ref="dataForm"
                 @keyup.enter.native="dataFormSubmit()"
                 label-width="80px"
                 >
            <el-form-item label="品牌名" prop="name">
                <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
            </el-form-item>
            <el-form-item label="品牌logo地址" prop="logo">
                <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input>
            </el-form-item>
            <el-form-item label="介紹" prop="descript">
                <el-input v-model="dataForm.descript" placeholder="介紹"></el-input>
            </el-form-item>
            <el-form-item label="顯示狀態" prop="showStatus">
                <el-switch
                           v-model="dataForm.showStatus"
                           active-color="#13ce66"
                           inactive-color="#ff4949"
                           >
                </el-switch>
            </el-form-item>
            <el-form-item label="檢索首字母" prop="firstLetter">
                <el-input
                          v-model="dataForm.firstLetter"
                          placeholder="檢索首字母"
                          ></el-input>
            </el-form-item>
            <el-form-item label="排序" prop="sort">
                <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
            </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
            <el-button @click="visible = false">取消</el-button>
            <el-button type="primary" @click="dataFormSubmit()">確定</el-button>
        </span>
    </el-dialog>
</template>

<script>
    export default {
        data() {
            return {
                visible: false,
                dataForm: {
                    brandId: 0,
                    name: "",
                    logo: "",
                    descript: "",
                    showStatus: "",
                    firstLetter: "",
                    sort: "",
                },
                dataRule: {
                    name: [{ required: true, message: "品牌名不能為空", trigger: "blur" }],
                    logo: [
                        { required: true, message: "品牌logo地址不能為空", trigger: "blur" },
                    ],
                    descript: [
                        { required: true, message: "介紹不能為空", trigger: "blur" },
                    ],
                    showStatus: [
                        {
                            required: true,
                            message: "顯示狀態[0-不顯示;1-顯示]不能為空",
                            trigger: "blur",
                        },
                    ],
                    firstLetter: [
                        { required: true, message: "檢索首字母不能為空", trigger: "blur" },
                    ],
                    sort: [{ required: true, message: "排序不能為空", trigger: "blur" }],
                },
            };
        },
        methods: {
            init(id) {
                this.dataForm.brandId = id || 0;
                this.visible = true;
                this.$nextTick(() => {
                    this.$refs["dataForm"].resetFields();
                    if (this.dataForm.brandId) {
                        this.$http({
                            url: this.$http.adornUrl(
                                `/product/brand/info/${this.dataForm.brandId}`
                            ),
                            method: "get",
                            params: this.$http.adornParams(),
                        }).then(({ data }) => {
                            if (data && data.code === 0) {
                                this.dataForm.name = data.brand.name;
                                this.dataForm.logo = data.brand.logo;
                                this.dataForm.descript = data.brand.descript;
                                this.dataForm.showStatus = data.brand.showStatus;
                                this.dataForm.firstLetter = data.brand.firstLetter;
                                this.dataForm.sort = data.brand.sort;
                            }
                        });
                    }
                });
            },
            // 表單提交
            dataFormSubmit() {
                this.$refs["dataForm"].validate((valid) => {
                    if (valid) {
                        this.$http({
                            url: this.$http.adornUrl(
                                `/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
                            ),
                            method: "post",
                            data: this.$http.adornData({
                                brandId: this.dataForm.brandId || undefined,
                                name: this.dataForm.name,
                                logo: this.dataForm.logo,
                                descript: this.dataForm.descript,
                                showStatus: this.dataForm.showStatus,
                                firstLetter: this.dataForm.firstLetter,
                                sort: this.dataForm.sort,
                            }),
                        }).then(({ data }) => {
                            if (data && data.code === 0) {
                                this.$message({
                                    message: "操作成功",
                                    type: "success",
                                    duration: 1500,
                                    onClose: () => {
                                        this.visible = false;
                                        this.$emit("refreshDataList");
                                    },
                                });
                            } else {
                                this.$message.error(data.msg);
                            }
                        });
                    }
                });
            },
        },
    };
</script>


2、添加上傳

這里我們選擇將圖片放置到阿里云上,使用物件存盤, 阿里云上使使用物件存盤方式:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tBhSavFA-1632494568629)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924214001988.png)]

  • 創建Bucket(作為專案)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QkXPi5vD-1632494568631)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924214202972.png)]

  • 上傳檔案:上傳成功后,取得圖片的URL

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kXcqdpbX-1632494568633)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924214424853.png)]

  • 這種方式是手動上傳圖片,實際上我們可以在程式中設定自動上傳圖片到阿里云物件存盤,

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AW1Pm2YC-1632494568634)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924214451666.png)]

上傳的賬號資訊存盤在應用服務器 上傳先找應用服務器要一個policy上傳策略,生成防偽簽名

  • 使用代碼上傳 查看阿里云關于檔案上傳的幫助:

    https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.768.549d59aaWuZMGJ

    • achangmall-product/pom.xml中添加依賴包
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.10.2</version>
    </dependency>
    
    • 上傳檔案流

    使用檔案上傳,您可以將本地檔案上傳到OSS檔案,

    以下代碼用于將本地檔案examplefile.txt上傳到目標存盤空間examplebucket中exampledir目錄下的exampleobject.txt檔案,

    // yourEndpoint填寫Bucket所在地域對應的Endpoint,以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com,
    String endpoint = "yourEndpoint";
    // 阿里云賬號AccessKey擁有所有API的訪問權限,風險很高,強烈建議您創建并使用RAM用戶進行API訪問或日常運維,請登錄RAM控制臺創建RAM用戶,
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    
    // 創建OSSClient實體,
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    
    // 創建PutObjectRequest物件,
    // 依次填寫Bucket名稱(例如examplebucket)、Object完整路徑(例如exampledir/exampleobject.txt)和本地檔案的完整路徑,Object完整路徑中不能包含Bucket名稱,
    // 如果未指定本地路徑,則默認從示例程式所屬專案對應本地路徑中上傳檔案,
    PutObjectRequest putObjectRequest = new PutObjectRequest("examplebucket", "exampledir/exampleobject.txt", new File("D:\\localpath\\examplefile.txt"));
    
    // 如果需要上傳時設定存盤型別和訪問權限,請參考以下示例代碼,
    // ObjectMetadata metadata = new ObjectMetadata();
    // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
    // metadata.setObjectAcl(CannedAccessControlList.Private);
    // putObjectRequest.setMetadata(metadata);
    
    // 上傳檔案,
    ossClient.putObject(putObjectRequest);
    
    // 關閉OSSClient,
    ossClient.shutdown();     
    

    上面代碼的資訊可以通過如下查找:

    • endpoint的取值:

    • 點擊概覽就可以看到你的endpoint資訊,endpoint在這里就是上海等地區,如 oss-cn-qingdao.aliyuncs.com

    • bucket域名:

    • 就是簽名加上bucket,如achangmall0.oss-cn-hangzhou.aliyuncs.com
      accessKeyId和accessKeySecret需要創建一個RAM賬號:

      [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gGxMIFAU-1632494568636)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924214912162.png)]

    • 選上編程訪問 創建用戶完畢后,會得到一個“AccessKey ID”和“AccessKeySecret”,

      然后復制這兩個值到代碼的“AccessKey ID”和“AccessKeySecret”,

      另外還需要添加訪問控制權限:

      @Test
      void test0() throws FileNotFoundException {
          // Endpoint以杭州為例,其它Region請按實際情況填寫,
          String endpoint = "oss-cn-hangzhou.aliyuncs.com";
          // 云賬號AccessKey有所有API訪問權限,建議遵循阿里云安全最佳實踐,創建并使用RAM子賬號進行API訪問或日常運維,請登錄 https://ram.console.aliyun.com 創建,
          String accessKeyId = "你的accessKeyId";
          String accessKeySecret = "你的accessKeySecret";
      
          // 創建OSSClient實體,
          OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
      
          // 上傳檔案流,
          InputStream inputStream = new FileInputStream("C:\\Users\\PePe\\Pictures\\Camera Roll\\321.png");
          ossClient.putObject("achangmall0", "321.png", inputStream);
      
          // 關閉OSSClient,
          ossClient.shutdown();
          System.out.println("上傳成功.");
      }
      
    • 更為簡單的使用方式,是使用SpringCloud Alibaba

      https://github.com/alibaba/aliyun-spring-boot/blob/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample/README-zh.md

      • achangmall-common/pom.xml引入依賴

      具體的可以在maven中央倉庫查找

      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-alicloud-oss</artifactId>
          <version>2.1.1.RELEASE</version>
      </dependency>
      
      • 在組態檔中配置 OSS 服務對應的 accessKey、secretKey 和 endpoint,
          alicloud:
            access-key: xxx
            secret-key: xxx
            oss:
              endpoint: oss-cn-hangzhou.aliyuncs.com
      
      • 注入OSSClient并進行檔案上傳下載等操作
      @RunWith(SpringRunner.class)
      @SpringBootTest
      public class OssTest {
      
          @Resource
          private OSSClient ossClient;
      
          @Test
          void test1() throws FileNotFoundException {
              // 上傳檔案流,
              InputStream inputStream = new FileInputStream("C:\\Users\\PePe\\Pictures\\Camera Roll\\321.png");
              ossClient.putObject("achangmall0", "321.png", inputStream);
      
              // 關閉OSSClient,
              ossClient.shutdown();
              System.out.println("上傳完成...");
          }
      }
      

但是這樣來做還是比較麻煩,如果以后的上傳任務都交給achangmall-product來完成,顯然耦合度高,最好單獨新建一個Module來完成檔案上傳任務,

  • 創建第三方模塊

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1AAAstll-1632494568639)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924223000398.png)]

  • 添加依賴,將原來achangmall-common中的“spring-cloud-starter-alicloud-oss”依賴移動到該專案中
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alicloud-oss</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.achang.achangmall</groupId>
        <artifactId>achangmall-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  • 主啟動類@EnableDiscoveryClient // 在主啟動類中開啟服務的注冊和發現

  • 在nacos中注冊 在nacos創建命名空間“ achangmall-third-party ”

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Q28ssfNy-1632494568640)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924223417154.png)]

  • 在“ achangmall-third-party”命名空間中,創建“ achangmall-third-service.yml”檔案

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-78xlpcNq-1632494568642)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210924223642717.png)]

  • 撰寫組態檔 application.yml
server:
  port: 30000

spring:
  application:
    name: achangmall-third-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  • bootstrap.properties
spring.cloud.nacos.config.name=achangmall-third-service
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=ad0431b9-a77f-4220-bf61-b48c7e117250
spring.cloud.nacos.config.extension-configs[0].data-id=achangmall-third-service.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
  • 撰寫測驗類
@SpringBootTest
@RunWith(SpringRunner.class)
class AchangmallThirdServiceApplicationTests {
    @Resource
    OSSClient ossClient;

    @Test
    void contextLoads() throws FileNotFoundException {
        //上傳檔案流,
        InputStream inputStream = new FileInputStream("C:\\Users\\PePe\\Pictures\\Camera Roll\\123.jpg");
        ossClient.putObject("achangmall0", "333.jpg", inputStream);

        // 關閉OSSClient,
        ossClient.shutdown();
        System.out.println("上傳成功.");
    }
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xprgkA72-1632569865581)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925142123288.png)]


  • 改進:服務端簽名后直傳

采用JavaScript客戶端直接簽名(參見JavaScript客戶端簽名直傳)時,AccessKeyID和AcessKeySecret會暴露在前端頁面,因此存在嚴重的安全隱患,

因此,OSS提供了服務端簽名后直傳的方案,

  • 向服務器獲取到簽名,再去請求oss服務器

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ALS8Vv8B-1632569865620)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925142916018.png)]

  • 服務端簽名后直傳的原理如下:

用戶發送上傳Policy請求到應用服務器, 應用服務器回傳上傳Policy和簽名給用戶,

用戶直接上傳資料到OSS,

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7ppRPzbz-1632569865623)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925142939283.png)]

  • 在com.achang.achangmall.controller.OssController撰寫controller
/******
 @author 阿昌
 @create 2021-09-25 14:32
 *******
 */
@RestController
@RequestMapping("third-service/oss")
public class OssController {

    @Resource
    private OSSClient ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    public String endpoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    public String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    public String accessId;

    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    @GetMapping("/policy")
    public Map<String, String> getPolicy(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String host = "https://" + bucket + "." + endpoint; // host的格式為 bucketname.endpoint
        // callbackUrl為上傳回呼服務器的URL,請將下面的IP和Port配置為您自己的真實資訊,
        //        String callbackUrl = "http://88.88.88.88:8888";


        String dir = format.format(new Date())+"/"; // 用戶上傳檔案時指定的前綴,以日期格式存盤

        // 創建OSSClient實體,
        Map<String, String> respMap= null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            // PostObject請求最大可支持的檔案大小為5 GB,即CONTENT_LENGTH_RANGE為5*1024*1024*1024,
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);//生成協議秘鑰
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);//生成的協議秘鑰
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));


        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }
    
}
  • 測驗請求,http://localhost:30000/third-service/oss/policy,成功獲取

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LLWMloyp-1632569865627)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925145932172.png)]

  • 然后,我們通過gateway網關代理轉發,在上傳檔案時的訪問路徑為“ http://localhost:88/api/third/oss/policy”,

  • 配置網關

        - id: oss_route
          uri: lb://achangmall-third-service
          predicates:
            - Path=/api/third-service/**
          filters:
            - RewritePath=/api/third-service/(?<segment>.*),/$\{segment}
  • 訪問http://localhost:88/api/third-service/third-service/oss/policy,測驗是否可以轉發到我們的介面,如下成功訪問

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-d23vQJT6-1632569865630)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925151233915.png)]


  • 上傳組件

  • 放置專案提供的upload檔案夾到components/目錄下,一個是單檔案上傳,另外一個是多檔案上傳

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HH9a8Ydn-1632569865632)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925153610191.png)]

  • policy.js封裝一個Promise,發送/thirdparty/oss/policy請求,vue專案會自動加上api前綴

  • multiUpload.vue多檔案上傳,要改,改方式如下

  • singleUpload.vue單檔案上傳,

    • 要替換里面的action中的內容action=“http://achangmall0.oss-cn-hangzhou.aliyuncs.com”,你的阿里云指定的bucket域名

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fEXmw90m-1632569865633)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925153721592.png)]

  • singleUpload.vue代碼

<template>
  <div>
    <el-upload
      action="http://achangmall0.oss-cn-hangzhou.aliyuncs.com"
      :data="dataObj"
      list-type="picture"
      :multiple="false"
      :show-file-list="showFileList"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleUploadSuccess"
      :on-preview="handlePreview"
    >
      <el-button size="small" type="primary">點擊上傳</el-button>
      <div slot="tip" class="el-upload__tip">
        只能上傳jpg/png檔案,且不超過10MB
      </div>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="fileList[0].url" alt="" />
    </el-dialog>
  </div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from "@/utils";
export default {
  name: "singleUpload",
  props: {
    value: String,
  },
  computed: {
    imageUrl() {
      return this.value;
    },
    imageName() {
      if (this.value != null && this.value !== "") {
        return this.value.substr(this.value.lastIndexOf("/") + 1);
      } else {
        return null;
      }
    },
    fileList() {
      return [
        {
          name: this.imageName,
          url: this.imageUrl,
        },
      ];
    },
    showFileList: {
      get: function () {
        return (
          this.value !== null && this.value !== "" && this.value !== undefined
        );
      },
      set: function (newValue) {},
    },
  },
  data() {
    return {
      dataObj: {
        policy: "",
        signature: "",
        key: "",
        ossaccessKeyId: "",
        dir: "",
        host: "",
        // callback:'',
      },
      dialogVisible: false,
    };
  },
  methods: {
    emitInput(val) {
      this.$emit("input", val);
    },
    handleRemove(file, fileList) {
      this.emitInput("");
    },
    handlePreview(file) {
      this.dialogVisible = true;
    },
    beforeUpload(file) {
      let _self = this;
      return new Promise((resolve, reject) => {
        policy()
          .then((response) => {
            console.log("回應的資料", response);
            _self.dataObj.policy = response.policy;
            _self.dataObj.signature = response.signature;
            _self.dataObj.ossaccessKeyId = response.accessid;
            _self.dataObj.key = response.dir + getUUID() + "_${filename}";
            _self.dataObj.dir = response.dir;
            _self.dataObj.host = response.host;
            console.log("回應的資料222,,,", _self.dataObj);
            resolve(true);
          })
          .catch((err) => {
            reject(false);
          });
      });
    },
    handleUploadSuccess(res, file) {
      console.log("上傳成功...");
      this.showFileList = true;
      this.fileList.pop();
      this.fileList.push({
        name: file.name,
        url:
          this.dataObj.host +
          "/" +
          this.dataObj.key.replace("${filename}", file.name),
      });
      this.emitInput(this.fileList[0].url);
    },
  },
};
</script>
<style>
</style>
  • multiUpload.vue代碼
<template>
    <div>
        <el-upload
                   action="http://achangmall0.oss-cn-hangzhou.aliyuncs.com"
                   :data="dataObj"
                   :list-type="listType"
                   :file-list="fileList"
                   :before-upload="beforeUpload"
                   :on-remove="handleRemove"
                   :on-success="handleUploadSuccess"
                   :on-preview="handlePreview"
                   :limit="maxCount"
                   :on-exceed="handleExceed"
                   :show-file-list="showFile"
                   >
            <i class="el-icon-plus"></i>
        </el-upload>
        <el-dialog :visible.sync="dialogVisible">
            <img width="100%" :src="dialogImageUrl" alt />
        </el-dialog>
    </div>
</template>
<script>
    import { policy } from "./policy";
    import { getUUID } from "@/utils";
    export default {
        name: "multiUpload",
        props: {
            //圖片屬性陣列
            value: Array,
            //最大上傳圖片數量
            maxCount: {
                type: Number,
                default: 30,
            },
            listType: {
                type: String,
                default: "picture-card",
            },
            showFile: {
                type: Boolean,
                default: true,
            },
        },
        data() {
            return {
                dataObj: {
                    policy: "",
                    signature: "",
                    key: "",
                    ossaccessKeyId: "",
                    dir: "",
                    host: "",
                    uuid: "",
                },
                dialogVisible: false,
                dialogImageUrl: null,
            };
        },
        computed: {
            fileList() {
                let fileList = [];
                for (let i = 0; i < this.value.length; i++) {
                    fileList.push({ url: this.value[i] });
                }
                return fileList;
            },
        },
        mounted() {},
        methods: {
            emitInput(fileList) {
                let value = [];
                for (let i = 0; i < fileList.length; i++) {
                    value.push(fileList[i].url);
                }
                this.$emit("input", value);
            },
            handleRemove(file, fileList) {
                this.emitInput(fileList);
            },
            handlePreview(file) {
                this.dialogVisible = true;
                this.dialogImageUrl = file.url;
            },
            beforeUpload(file) {
                let _self = this;
                return new Promise((resolve, reject) => {
                    policy()
                        .then((response) => {
                        console.log("這是什么${filename}");
                        _self.dataObj.policy = response.data.policy;
                        _self.dataObj.signature = response.data.signature;
                        _self.dataObj.ossaccessKeyId = response.data.accessid;
                        _self.dataObj.key = response.data.dir + getUUID() + "_${filename}";
                        _self.dataObj.dir = response.data.dir;
                        _self.dataObj.host = response.data.host;
                        resolve(true);
                    })
                        .catch((err) => {
                        console.log("出錯了...", err);
                        reject(false);
                    });
                });
            },
            handleUploadSuccess(res, file) {
                this.fileList.push({
                    name: file.name,
                    // url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替換${filename}為真正的檔案名
                    url:
                    this.dataObj.host +
                    "/" +
                    this.dataObj.key.replace("${filename}", file.name),
                });
                this.emitInput(this.fileList);
            },
            handleExceed(files, fileList) {
                this.$message({
                    message: "最多只能上傳" + this.maxCount + "張圖片",
                    type: "warning",
                    duration: 1000,
                });
            },
        },
    };
</script>
<style>
</style>
  • policy.js代碼

/third-service/third-service/oss/policy為你88網關代理的oss服務路由uri地址

import http from '@/utils/httpRequest.js'
export function policy() {
    return new Promise((resolve, reject) => {
        http({
            #修改你88網關代理的oss服務路由uri地址
            url: http.adornUrl("/third-service/third-service/oss/policy"),
            method: "get",
            params: http.adornParams({})
        }).then(({ data }) => {
            resolve(data);
        })
    });
}

  • 我們在后端準備好了簽名controller,那么前端是在哪里獲取的呢

而檔案上傳前呼叫的方法::before-upload=“beforeUpload”發現該方法回傳了一個new Promise,呼叫了policy(),該方法是policy.js中的 import { policy } from “./policy”;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vUtYJzG6-1632569865634)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154037083.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e9uPor6J-1632569865635)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154122803.png)]

  • 在vue中看是response.data.policy,在控制臺看response.policy,所以去java里面改回傳值為R,return R.ok().put(“data”,respMap);
  • 也可以像上面,阿昌這樣子直接修改前端代碼,選擇一個即可

  • 阿里云開啟跨域
    開始執行上傳,但是在上傳程序中,出現了跨域請求問題:
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XndpSwcj-1632569865637)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154348021.png)]

    這又是一個跨域的問題,解決方法就是在阿里云上開啟跨域訪問:
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Z7sHad9c-1632569865638)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154413963.png)]

  • 配置oss跨域

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-biv0fprv-1632569865639)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154518787.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nX1EsR4Z-1632569865640)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154606416.png)]

再次執行檔案上傳, 注意上傳時他的key變成了response.dir +getUUID()+"_${filename}";

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DVYJSPjs-1632569865641)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925154634920.png)]


3、表單校驗&自定義校驗器

  • 修改brand-add-or-update如下: :active-value=“1” :inactive-value=“0” # 激活為1,不激活為0
<el-switch
           v-model="dataForm.showStatus"
           active-color="#13ce66"
           inactive-color="#ff4949"
           :active-value="1"
           :inactive-value="0"
           >
</el-switch>
  • 添加表單校驗&自定義校驗器
<script>
    firstLetter: [
        {
            validator: (rule, value, callback) => {
                if (value == "") {
                    callback(new Error("首字母必須填寫"));
                } else if (!/^[a-zA-Z]$/.test(value)) {
                    callback(new Error("首字母必須a-z或者A-Z之間"));
                } else {
                    callback();
                }
            },
            trigger: "blur",
        },
    ],
        sort: [{validator: (rule, value, callback) => {
            if (value == "") {
                callback(new Error("排序欄位必須填寫"));
            } else if (!Number.isInteger(parseInt(value)) || parseInt(value) < 0){
                callback(new Error("排序欄位必須是一個整數"));
            } else {
                callback();
            }
        }, trigger: "blur" }]
</script>
  • 完整brand-add-or-update修改代碼
<template>
    <el-dialog
               :title="!dataForm.brandId ? '新增' : '修改'"
               :close-on-click-modal="false"
               :visible.sync="visible"
               >
        <el-form
                 :model="dataForm"
                 :rules="dataRule"
                 ref="dataForm"
                 @keyup.enter.native="dataFormSubmit()"
                 label-width="80px"
                 >
            <el-form-item label="品牌名" prop="name">
                <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
            </el-form-item>
            <el-form-item label="品牌logo地址" prop="logo">
                <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
                <singleUpload v-model="dataForm.logo"></singleUpload>
            </el-form-item>
            <el-form-item label="介紹" prop="descript">
                <el-input v-model="dataForm.descript" placeholder="介紹"></el-input>
            </el-form-item>
            <el-form-item label="顯示狀態" prop="showStatus">
                <el-switch
                           v-model="dataForm.showStatus"
                           active-color="#13ce66"
                           inactive-color="#ff4949"
                           :active-value="1"
                           :inactive-value="0"
                           >
                </el-switch>
            </el-form-item>
            <el-form-item label="檢索首字母" prop="firstLetter">
                <el-input
                          v-model="dataForm.firstLetter"
                          placeholder="檢索首字母"
                          ></el-input>
            </el-form-item>
            <el-form-item label="排序" prop="sort">
                <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
            </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
            <el-button @click="visible = false">取消</el-button>
            <el-button type="primary" @click="dataFormSubmit()">確定</el-button>
        </span>
    </el-dialog>
</template>

<script>
    import singleUpload from "@/components/upload/singleUpload";

    export default {
        components: {
            singleUpload: singleUpload,
        },
        data() {
            return {
                visible: false,
                dataForm: {
                    brandId: 0,
                    name: "",
                    logo: "",
                    descript: "",
                    showStatus: "",
                    firstLetter: "",
                    sort: "",
                },
                dataRule: {
                    name: [{ required: true, message: "品牌名不能為空", trigger: "blur" }],
                    logo: [
                        { required: true, message: "品牌logo地址不能為空", trigger: "blur" },
                    ],
                    descript: [
                        { required: true, message: "介紹不能為空", trigger: "blur" },
                    ],
                    showStatus: [
                        {
                            required: true,
                            message: "顯示狀態[0-不顯示;1-顯示]不能為空",
                            trigger: "blur",
                        },
                    ],
                    firstLetter: [
                        {
                            validator: (rule, value, callback) => {
                                if (value == "") {
                                    callback(new Error("首字母必須填寫"));
                                } else if (!/^[a-zA-Z]$/.test(value)) {
                                    callback(new Error("首字母必須a-z或者A-Z之間"));
                                } else {
                                    callback();
                                }
                            },
                            trigger: "blur",
                        },
                    ],
                    sort: [
                        {
                            validator: (rule, value, callback) => {
                                if (value == "") {
                                    callback(new Error("排序欄位必須填寫"));
                                } else if (
                                    !Number.isInteger(parseInt(value)) ||
                                    parseInt(value) < 0
                                ) {
                                    callback(new Error("排序欄位必須是一個整數"));
                                } else {
                                    callback();
                                }
                            },
                            trigger: "blur",
                        },
                    ],
                },
            };
        },
        methods: {
            init(id) {
                this.dataForm.brandId = id || 0;
                this.visible = true;
                this.$nextTick(() => {
                    this.$refs["dataForm"].resetFields();
                    if (this.dataForm.brandId) {
                        this.$http({
                            url: this.$http.adornUrl(
                                `/product/brand/info/${this.dataForm.brandId}`
                            ),
                            method: "get",
                            params: this.$http.adornParams(),
                        }).then(({ data }) => {
                            if (data && data.code === 0) {
                                this.dataForm.name = data.brand.name;
                                this.dataForm.logo = data.brand.logo;
                                this.dataForm.descript = data.brand.descript;
                                this.dataForm.showStatus = data.brand.showStatus;
                                this.dataForm.firstLetter = data.brand.firstLetter;
                                this.dataForm.sort = data.brand.sort;
                            }
                        });
                    }
                });
            },
            // 表單提交
            dataFormSubmit() {
                this.$refs["dataForm"].validate((valid) => {
                    if (valid) {
                        this.$http({
                            url: this.$http.adornUrl(
                                `/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
                            ),
                            method: "post",
                            data: this.$http.adornData({
                                brandId: this.dataForm.brandId || undefined,
                                name: this.dataForm.name,
                                logo: this.dataForm.logo,
                                descript: this.dataForm.descript,
                                showStatus: this.dataForm.showStatus,
                                firstLetter: this.dataForm.firstLetter,
                                sort: this.dataForm.sort,
                            }),
                        }).then(({ data }) => {
                            if (data && data.code === 0) {
                                this.$message({
                                    message: "操作成功",
                                    type: "success",
                                    duration: 1500,
                                    onClose: () => {
                                        this.visible = false;
                                        this.$emit("refreshDataList");
                                    },
                                });
                            } else {
                                this.$message.error(data.msg);
                            }
                        });
                    }
                });
            },
        },
    };
</script>

4、JSR303資料校驗

  • 問題引入

    填寫form時應該有前端校驗,后端也應該有校驗 前端 前端的校驗是element-ui表單驗證 Form 組件提供了表單驗證的功能,只需要通過 rules 屬性傳入約定的驗證規則,并將 Form-Item 的 prop 屬性設定為需校驗的欄位名即可,

  • 如果你的springboot版本沒有默認引入,就匯入依賴

<!--jsr3引數校驗器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

里面依賴了hibernate-validator 在非空處理方式上提供了@NotNull,@NotBlank和@NotEmpty

在物體類的屬性上使用如上的注解等

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
	 * 品牌id
	 */
    @TableId
    private Long brandId;
    /**
	 * 品牌名
	 */
    @NotBlank
    private String name;
    /**
	 * 品牌logo地址
	 */
    private String logo;
    /**
	 * 介紹
	 */
    private String descript;
    /**
	 * 顯示狀態[0-不顯示;1-顯示]
	 */
	@NotNull
    private Integer showStatus;
    /**
	 * 檢索首字母
	 */
    @NotEmpty
    private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(0)
	private Integer sort;

}
  • 步驟2:controller中加校驗注解@Valid,開啟校驗,
@RequestMapping("/save")
public R save(@RequestBody @Valid BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}
  • 可以在添加注解的時候,修改message

    @NotBlank(message = "品牌名必須非空")
    private String name; 
    
  • 但是這種回傳的錯誤結果并不符合我們的業務需要,

  • 步驟3:給校驗的Bean后,緊跟一個BindResult,就可以獲取到校驗的結果,拿到校驗的結果,就可以自定義的封裝,

@RequestMapping("/save")
public R save(@RequestBody @Valid BrandEntity brand, BindingResult result){
    if( result.hasErrors()) {
        Map<String, String> map = new HashMap<>();
        //1.獲取錯誤的校驗結果
        result.getFieldErrors().forEach((item) -> {
            //獲取發生錯誤時的message
            String message = item.getDefaultMessage();
            //獲取發生錯誤的欄位
            String field = item.getField();
            map.put(field, message);
        });
        return R.error(400, "提交的資料不合法").put("data", map);
    }
    brandService.save(brand);
    return R.ok();
}

這種是針對于該請求設定了一個內容校驗,如果針對于每個請求都單獨進行配置,顯然不是太合適,實際上可以統一的對于例外進行處理

  • 統一例外處理@ControllerAdvice步驟4:統一例外處理

可以使用SpringMvc所提供的@ControllerAdvice,通過“basePackages”能夠說明處理哪些路徑下的例外,

在com.achang.achangmall.product.exception.AchangExceptionControllerAdvice撰寫

@Slf4j
@RestControllerAdvice(basePackages = "com.achang.achangmall.product")
public class AchangExceptionControllerAdvice {

    @ExceptionHandler(value = Exception.class) // 也可以回傳ModelAndView
    public R handleValidException(MethodArgumentNotValidException exception) {

        Map<String, String> map = new HashMap<>();
        // 獲取資料校驗的錯誤結果
        BindingResult bindingResult = exception.getBindingResult();
        bindingResult.getFieldErrors().forEach(fieldError -> {
            String message = fieldError.getDefaultMessage();
            String field = fieldError.getField();
            map.put(field, message);
        });

        log.error("資料校驗出現問題{},例外型別{}", exception.getMessage(), exception.getClass());

        return R.error(400, "資料校驗出現問題").put("data", map);
    }
}
  • 測驗: http://localhost:88/api/product/brand/save

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fVEYvtja-1632569865643)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925174005562.png)]

如果沒有用,可能是spring沒有掃描到,在主函式上添加@ComponentScan("com.achang.achangmall.product")

  • 默認例外處理
    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        log.error("未知例外{},例外型別{}",throwable.getMessage(),throwable.getClass());
        return R.error(400,"資料校驗出現問題");
    }
  • 錯誤狀態碼

上面代碼中,針對于錯誤狀態碼,是我們進行隨意定義的,然而正規開發程序中,錯誤狀態碼有著嚴格的定義規則,如該在專案中我們的錯誤狀態碼定義

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-E3QMr2jz-1632569865643)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925173220257.png)]

  • 為了定義這些錯誤狀態碼,我們可以單獨定義一個常量類,用來存盤這些錯誤狀態碼

com.achang.common.exception,在通用模塊中

/***
 * 錯誤碼和錯誤資訊定義類
 * 1. 錯誤碼定義規則為5為數字
 * 2. 前兩位表示業務場景,最后三位表示錯誤碼,例如:100001,10:通用 001:系統未知例外
 * 3. 維護錯誤碼后需要維護錯誤描述,將他們定義為列舉形式
 * 錯誤碼串列:
 *  10: 通用
 *      001:引數格式校驗
 *  11: 商品
 *  12: 訂單
 *  13: 購物車
 *  14: 物流
 */
public enum BizCodeEnum {
    UNKNOW_EXEPTION(10000,"系統未知例外"),

    VALID_EXCEPTION( 10001,"引數格式校驗失敗");

    private int code;
    private String msg;

    BizCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

5、分組校驗功能(完成多場景的復雜校驗)

  • 給校驗注解,標注上groups,指定什么情況下才需要進行校驗

groups里面的內容要以介面的形式顯示出來

如:指定在更新和添加的時候,都需要進行校驗,新增時不需要帶id,修改時必須帶id

  • 在通用模塊中創建校驗用的空介面,他只是個標識

achangmall-common中的com.achang.common.vail

//更新校驗
public interface UpdateVail {}

//新增校驗
public interface AddVail {}
  • 在實體類上groups標志介面標識

在這種情況下,沒有指定分組的校驗注解,默認是不起作用的,想要起作用就必須要加groups,

@NotNull(message = "修改必須定制品牌id", groups = {UpdateVailGroup.class})
@Null(message = "新增不能指定id", groups = {AddVailGroup.class})
private Long brandId;
  • 業務方法引數上使用@Validated注解,并用@Validated指定使用校驗的介面標識

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WkMeaSFd-1632569865645)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925181029419.png)]

  • 分組情況下,校驗注解生效問題

  • 默認情況下,在分組校驗情況下,沒有指定指定分組的校驗注解,將不會生效,它只會在不分組的情況下生效,


6、自定義校驗功能

  • 場景

    • 要校驗showStatus的01狀態,可以用正則,但我們可以利用其他方式解決復雜場景,比如我們想要下面的場景
  • 添加依賴

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
  • 撰寫自定義的校驗注解
/**
 * 自定義校驗注解
 */
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    // 使用該屬性去Validation.properties中取
    String message() default "{com.atguigu.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] value() default {};//傳入可通過校驗的值[]
}
  • 自定義校驗器
/**
 * 自定義校驗器
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {//泛型左邊:自定義校驗注解,泛型右邊:校驗的型別

    private Set<Integer> set=new HashSet<>();

    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] value = constraintAnnotation.value();//獲取可通過的值
        for (int i : value) {
            set.add(i);
        }
    }

    @Override//左側:傳入需要校驗的值
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return  set.contains(value);
    }
}
  • 關聯校驗器和校驗注解

一個校驗注解可以匹配多個校驗器

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8nENMYt0-1632569865646)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210925193339258.png)]

  • 使用實體
/**
* 顯示狀態[0-不顯示;1-顯示]
* 標識只能接受0,1;其他值都不能通過校驗
*/
@ListValue(value = {0,1},groups ={AddGroup.class})
private Integer showStatus;

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/303061.html

標籤:其他

上一篇:vue專案實作登陸 注冊效果

下一篇:js的reduce的一些用法總結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more