1. 程式人生 > 實用技巧 >【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API

【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API

【手摸手,帶你搭建前後端分離商城系統】01 搭建基本程式碼框架、生成一個基本API

通過本教程的學習,將帶你從零搭建一個商城系統。

當然,這個商城涵蓋了很多流行的知識點技術核心

我可以學習到什麼?

  • SpringBoot
    • 鑑權與認證、token、有關許可權的相關的內容。
    • 優雅的利用OSS 上傳檔案
    • API 線上生成文件
  • Redis
    • Redis 基本使用
    • Redis 快取存放使用者token等
  • Docker
    • 容器技術的使用
    • SpringBoot 專案打包docker image
  • ElasticSearch
    • Elasticsearch 搜尋引擎框架
  • RabbitMQ
    • 訊息佇列整合SpringBoot
  • Linux
    • 部署相關的Linux 命令的學習與使用

等等等。。。 不用猶豫了,和我一起來吧!

開始搭建

首先、當然以 maven 作為專案管理工具、以我們最熟悉的 SpringBoot 作為專案腳手架,幫助我們快速搭建起專案框架。

本小結需要了解的技術棧有:

  • maven 模組化的實現
  • 引入Mybatis-plus 簡化CRUD
  • 設計基本的 許可權三張表

建立一個maven 專案

我這裡使用的是 IDEA ,希望朋友們也跟著我一起,當然其他優秀的整合開發工具也是很牛逼的,反正用著順手就行!

建立一個新的專案、自定義專案名稱,並且鍵入你自己的 group id 以及 artifactId

因為我們採用的是專案模組化的實現,父類就只是一個空殼,一般定義專案裡面需要的所有 依賴資訊 以及 版本資訊 等。引入我們所有下面的子專案都會用到的 公共依賴包 這些。

定義我們將要使用的 Spring-boot 版本,我們這是使用 2.1.3

    <!-- springboot 2.3.0 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>

劃分模組

這裡依舊參考 mall 專案對於模組的規劃,簡單介紹一下。

  • mall-admin 後臺管理模組
  • mall-common 公共包模組
  • mall-mbg mybatis-plus 生成 mapper、model 等
  • mall-security 鑑權、授權模組

暫時就先劃分這麼幾個吧!等後面用到了我們再劃分即可。

父類定義版本和基本模組

<packaging>pom</packaging>

父類作為一個空殼,其最主要的目的是模組化的劃分。它裡面其實是不包含程式碼的,所以將它的打包方式改為 pom

就可以將父類下的 src 目錄刪掉了。

首先定義幾個每個模組都會使用到的依賴。比如 aop切面 test 測試模組 等依賴資訊。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    	<!-- 省略其他。。。 -->
</dependencies>

使用 dependencyManagement

使用這個標籤是為了我們依賴的 統一管理。防止兩個模組引用不同版本的依賴,而導致打包衝突或者執行衝突等問題。

最大的好處也在於:父類定義好需要使用的依賴後、子類引用無需版本號。

    <!-- 變數定義,定義版本號 -->
	<properties>
        <java.version>1.8</java.version>
        <mybatis.plus.version>3.3.2</mybatis.plus.version>
        <hutool.version>5.4.0</hutool.version>
        <mysql.connector.version>8.0.20</mysql.connector.version>
    </properties>

	<!-- 父類定義的所有的包管理、子類統一使用、而不用寫明版本號 -->
    <dependencyManagement>
        <dependencies>
            <!-- mybatis-plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>
            <!--Hutool Java工具包-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <!--mysql 驅動-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

到這裡,我們基本的父類已經構建完成了,我們可以開始構建 模組 了。

構建模組

直接在專案上右鍵 new model ,建立一個新的模組。

設計許可權三張表

建立後臺使用者表、用來儲存使用者資訊。

CREATE TABLE `ums_admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '後臺管理使用者',
  `username` varchar(64) NOT NULL COMMENT '使用者名稱',
  `password` varchar(64) NOT NULL COMMENT '密碼',
  `icon` varchar(1024) NOT NULL COMMENT '頭像',
  `lock` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0鎖定1正常使用',
  `email` varchar(128) NOT NULL COMMENT '電子郵箱',
  `nick_name` varchar(32) NOT NULL COMMENT '暱稱',
  `note` varchar(64) NOT NULL COMMENT '備註資訊',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `login_time` datetime DEFAULT NULL COMMENT '最後登入時間',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除標記',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

建立後臺角色資訊表,儲存角色資訊。

CREATE TABLE `ums_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表',
  `name` varchar(64) NOT NULL COMMENT '角色名稱',
  `description` varchar(128) NOT NULL COMMENT '角色描述',
  `admin_count` smallint(6) NOT NULL DEFAULT '0' COMMENT '後臺使用者數量',
  `lock` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0鎖定 1正常使用',
  `sort` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序',
  `create_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '建立時間',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除狀態0 1正常',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

建立後臺選單許可權表,用於儲存選單關係。

CREATE TABLE `ums_menu` (
  `id` int(11) NOT NULL COMMENT '選單表',
  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父級ID',
  `title` varchar(11) NOT NULL COMMENT '選單標題',
  `level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '選單級別',
  `sort` smallint(6) NOT NULL DEFAULT '0' COMMENT '選單排序',
  `name` varchar(64) NOT NULL COMMENT '前端VUE 名稱',
  `icon` varchar(32) NOT NULL COMMENT '圖示',
  `hidden` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否隱藏 0隱藏 1展示',
  `create_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '邏輯刪除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

我們的許可權還是通過 使用者-角色-許可權 最經典的設計來進行了,這樣的許可權也適用大多數系統。

實現一個 REST API

因為是前後端分離專案,我們所有的請求需要經過 Controller , 這也是現在絕大多數系統所使用的架構、這一套系統依舊沿用Restful 風格的介面。並且按照 如下圖的架構進行 API 的書寫。

Restful 風格介面

  • POST /user/ 新增一個使用者
  • GET /user/1 查詢ID 為 1 的使用者資訊
  • GET /user/ 查詢所有的使用者資訊
  • DELETE /user/1 刪除ID 為 1 的使用者資訊
  • PUT /user/1 修改ID 為 1 的使用者資訊
  • POST /user/page 當然就是按照傳入的條件進行分頁了

Restful 架構設定

開始寫程式碼吧

從上面的架構圖,我們已經可以寫出一個基本的CRUD Controller

包命名規則

一個合格的程式猿,寫的程式碼不僅給人一種舒服的感覺。而且包名命名等也是一個可以學習的點。

  • mapper 當然就是mybatis mapper 放置的位置。
  • model 一款 ORM 框架對重要的就是:資料庫物件與java物件的對映。
  • controller 介面api 的位置。
  • pojo 放置一些入參類、包裝類等。
  • config 當然就是放置一些配置類。

Controller

我們以 ums_admin 後臺使用者表作為示例,其實這些都是可以生成的~ 具體看 開啟偷懶模式

  • Controller 包含基本的 CRUD 介面。
  • Restful 風格介面資訊,更加容易理解介面含義。
  • Swagger 生成基本的API 文件資訊,以及測試介面。
  • 校驗引數完整性!
@Api(tags = "ApiUmsAdminController",description = "後臺使用者")
@RestController
@RequestMapping("/umsAdmin")
@Validated
public class ApiUmsAdminController {

    @Autowired
    private UmsAdminService umsAdminService;


    /**
     * <p>查詢所有後臺使用者
     * <p>author: mrc
     *
     * @return xyz.chaobei.common.api.CommonResult
     * @since 2020-10-12 11:18:42
     **/
    @ApiOperation("查詢所有後臺使用者")
    @GetMapping("/")
    public CommonResult getAll() {

        List<UmsAdminModel> allList = umsAdminService.findAll();
        return CommonResult.success(allList);
    }

    /**
     * <p>預設分頁請求後臺使用者
     * <p>author: mrc
     *
     * @param pageAO 分頁查詢引數
     * @since 2020-10-12 11:18:42
     * @return xyz.chaobei.common.api.CommonResult
     **/
    @ApiOperation("預設分頁請求後臺使用者")
    @PostMapping("/page")
    public CommonResult paging(@RequestBody @ApiParam("分頁查詢引數") UmsAdminPageAO pageAO) {

        Page<UmsAdminModel> allList = umsAdminService.findPage(pageAO);
        return CommonResult.success(allList);
    }

    /**
     * <p>儲存一個後臺使用者
     * <p>author: mrc
     *
     * @param params 儲存欄位
     * @since 2020-10-12 11:18:42
     * @return xyz.chaobei.common.api.CommonResult
     **/
    @ApiOperation("儲存一個後臺使用者")
    @PostMapping("/")
    public CommonResult save(@RequestBody @Valid @ApiParam("儲存欄位") UmsAdminSaveAO params) {

        boolean isSave = umsAdminService.save(params);
        return CommonResult.result(isSave);
    }


    /**
     * <p>修改一個後臺使用者
     * <p>author: mrc
     *
     * @param id 被修改的ID
     * @param params 被修改的欄位
     * @since 2020-10-12 11:18:42
     * @return xyz.chaobei.common.api.CommonResult
     **/
    @ApiOperation("修改一個後臺使用者")
    @PutMapping("/{id}")
    public CommonResult update(@PathVariable("id") @ApiParam("被修改的ID") Integer id, @Valid @RequestBody @ApiParam("被修改的欄位") UmsAdminSaveAO params) {

        boolean isUpdate = umsAdminService.updateById(params,id);
        return CommonResult.result(isUpdate);
    }

    /**
     * <p>刪除一個後臺使用者
     * <p>author: mrc
     *
     * @param id 被刪除的ID
     * @since 2020-10-12 11:18:42
     * @return xyz.chaobei.common.api.CommonResult
     **/
    @ApiOperation("刪除一個後臺使用者")
    @DeleteMapping("/{id}")
    public CommonResult delete(@Valid @NotNull @PathVariable("id") @ApiParam("被刪除的ID") Integer id) {

        boolean isDelete = umsAdminService.deleteById(id);
        return CommonResult.result(isDelete);
    }

}

SaveAO

SaveAO 一般就是前端 填寫表單入參的資訊 ,當然我們能直接使用 DO 進行攜帶引數。那樣不安全。AO 將引數從 Controller

攜帶後,通過 javax.validation.Valid 對欄位進行校驗後、方可進行下一步。

  • SaveAO 將引數從 Controller 傳遞到 Service 處理邏輯
  • Controller 入參的時候,檢驗 SaveAO 所包含的引數。
    • @NotBlank
    • @NotNull
    • 略...
  • @ApiModelProperty 說明引數註釋資訊
@Getter
@Setter
public class UmsAdminSaveAO {

        
    /**
     * 使用者名稱
     */
    @NotBlank
    @ApiModelProperty("使用者名稱")
    private String username;
        
    /**
     * 密碼
     */
    @NotBlank
    @ApiModelProperty("密碼")
    private String password;
        
    /**
     * 頭像
     */
    @ApiModelProperty("頭像")
    private String icon;
        
    /**
     * 0鎖定1正常使用
     */
    @NotNull
    @ApiModelProperty("0鎖定1正常使用")
    private Integer lock;
        
    /**
     * 電子郵箱
     */
    @NotBlank
    @ApiModelProperty("電子郵箱")
    private String email;
        
    /**
     * 暱稱
     */
    @ApiModelProperty("暱稱")
    private String nickName;
        
    /**
     * 備註資訊
     */
    @ApiModelProperty("備註資訊")
    private String note;
                
}

當然。這裡的所有引數都是可以自定義的。你想要哪些,就生成哪些~

Service

  • Service 負責將 Controller 傳遞的 AO 複製到 DO(Database Object)
  • 呼叫 Mapper 的方法進行持久化。
  • Service 返回一個 成功或者失敗的標誌。
  • 邏輯異常,丟擲一個異常資訊【例如這個ID 找不到使用者。。。】,全域性捕獲後,返回給前端進行提示。
@Service
public class UmsAdminServiceimpl implements UmsAdminService {

    @Autowired
    private UmsAdminMapper umsAdminMapper;

    @Override
    public List<UmsAdminModel> findAll() {
        return umsAdminMapper.selectList(null);
    }

    @Override
    public Page<UmsAdminModel> findPage(UmsAdminPageAO pageAO) {

        Page page = new Page(pageAO.getCurrent(),pageAO.getSize());
        QueryWrapper wrapper = new QueryWrapper();

        wrapper.eq("`username`", pageAO.getUsername());
        wrapper.eq("`lock`", pageAO.getLock());
        wrapper.eq("`note`", pageAO.getNote());

        umsAdminMapper.selectPage(page, wrapper);

        return page;
    }

    @Override
    public boolean save(UmsAdminSaveAO params) {

        UmsAdminModel model = new UmsAdminModel();
        BeanUtils.copyProperties(params,model);
        /**
         * 你的邏輯寫在這裡
         */
        int num = umsAdminMapper.insert(model);

        return SqlHelper.retBool(num);
    }

    @Override
    public boolean updateById(UmsAdminSaveAO params, Integer id) {

        UmsAdminModel model = new UmsAdminModel();
        BeanUtils.copyProperties(params,model);

        /**
         * 你的邏輯寫在這裡
         */
        model.setId(id);
        int num = umsAdminMapper.updateById(model);

        return SqlHelper.retBool(num);
    }

    @Override
    public boolean deleteById(Integer id) {

        /**
         * 你的邏輯寫在這裡
         */
        int num = umsAdminMapper.deleteById(id);
        return SqlHelper.retBool(num);
    }

}

Mapper

  • 繼承 Mybatis-Plus BaseMapper 獲得基礎CRUD 能力。
public interface UmsAdminMapper extends BaseMapper<UmsAdminModel> {
	// 繼承mybatis-plus 獲得基礎crud
}

Mybatis-Plus Config

主要是配置 mybatis 掃描的mapper 所在的位置。以及開啟事務的支援。

@Configuration
@EnableTransactionManagement
@MapperScan({"xyz.chaobei.mall.dao","xyz.chaobei.mall.mapper"})
public class MyBatisPlusConfig {

}

開啟偷懶模式

能不能有一種東西,給我生成這種重複的東西,而我只關注邏輯呢?

當然有了~

上面示例的程式碼都是用工具生成的~ 總不能一個一個敲出來吧~

學會偷懶其實也是一種好處。人類的發展不就是朝著偷懶的方向發展嘛

新增配置檔案

這已經是最後的幾個步驟了。新增配置檔案,主要是配置 mybatis-plus mapper 所在的位置。

以及配置我們的邏輯刪除、自動填充這兩個很好用的功能。

配置邏輯刪除

https://baomidou.com/guide/logic-delete.html

邏輯刪除有什麼好處呢?我覺得主要還是資料的完整性。上線以後、就算這條資料要被刪除,也只能是通過狀態隱藏起來,

並非真實刪除。

還有一個注意的點就是:既然配置了這個status ,那麼所有的表都應該有這個欄位。

#mybatis-plus 基礎配置
mybatis-plus:
  mapper-locations:
    - classpath:/dao/**/*.xml
    - classpath*:/mapper/**/*.xml
  global-config:
    db-config:
      logic-delete-field: status
      logic-not-delete-value: 1
      logic-delete-value: 0

配置自動填充功能

https://baomidou.com/guide/auto-fill-metainfo.html

一般情況下:我們每一條資料都會包含一個 時間欄位(建立、修改) 這樣的欄位每次要在:插入、修改的時候進行新增。

其實很難受的。所以還是偷個懶~ 讓程式碼幫我們完成。我這裡只有一個 createTime 需要填充,你可以參考官網再詳細一些。

所以:你的每個表都應該包含這個填充欄位

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {}
}

別忘了標識欄位。

    /**
     * 建立時間
     */
    @TableField(value = "`create_time`",fill = FieldFill.INSERT)
    private Date createTime;

測試介面程式碼

細心的朋友已經發現了。我們系統已經集成了 swagger

swagger 對於介面文件的生成和測試,簡直完美。程式碼寫好了,文件自然而然的被生成。並且可以在頁面上測試 介面通訊

簡直完美啊!

整合Swagger

考慮到 swagger 通用的配置類可能被多個模組所使用,所以我們首先建立一個 abstract class 讓子類重寫它的抽象方法。這樣就實現了一個通用的 swagger config

public abstract class BaseSwaggerConfig {

    @Bean
    public Docket createDocket() {
        // 獲取自定義配置
        SwaggerProperties properties = this.customSwagger();

        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                // api 生成基本資訊
                .apiInfo(this.buildApiInfo(properties))
                // 開啟一個端點
                .select()
                // 生成API 的包路徑
                .apis(RequestHandlerSelectors.basePackage(properties.getApiBasePackage()))
                // 路徑選擇
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    /**
     * 構建API 資訊方法,通過自定義的SwaggerProperties 轉化為 ApiInfo
     * 通過ApiInfoBuilder 構建一個api資訊。
     *
     * @param properties 自定義資訊
     * @return
     */
    private ApiInfo buildApiInfo(SwaggerProperties properties) {
        return new ApiInfoBuilder()
                // 標題
                .title(properties.getTitle())
                // 描述
                .description(properties.getDescription())
                // 聯絡人資訊
                .contact(new Contact(properties.getContactName(), properties.getContactUrl(), properties.getContactEmail()))
                // 版本資訊
                .version(properties.getVersion())
                .build();
    }
    /**
     * 自定義實現配置資訊
     *
     * @return
     */
    public abstract SwaggerProperties customSwagger();
}

Admin Swagger Config

讓我們的子類繼承通用的父類,並且重寫customSwagger 自定義一個配置類。填寫一些 api 的基本資訊。即可。

@EnableSwagger2 開啟Swagger 支援

SwaggerProperties 是自己定義的一個配置資訊類,使用者包裝如下的資訊。詳細見程式碼

@Configuration
@EnableSwagger2
public class AdminSwaggerConfig extends BaseSwaggerConfig {

    @Override
    public SwaggerProperties customSwagger() {
        return SwaggerProperties.builder()
                .title("mall-pro")
                .description("mall-pro 介面描述資訊")
                .apiBasePackage("xyz.chaobei.mall.controller")
                .contactName("mrc")
                .enableSecurity(false)
                .version("1.0")
                .build();
    }
}

訪問Swagger

啟動我們的main() 方法。讓這個專案跑起來!

訪問:http://localhost:8080/swagger-ui.html

基本的增刪改查,已經展現出來了。可以直接在這裡測試我們介面的連通性,真的特別方便。

測試這個一個新增的介面。

操作成功的返回資訊。狀態碼、以及提示語。

小結

學到這裡。你已經整合了一個基本的介面、並且測試通了介面的連通性。而且文件也不用自己手寫了,全部自動生成。

總結一下:我們學習和使用到了:

  • maven 子專案的搭建
  • mybatis-plus 的整合
  • mybatis-plus 自動填充功能的使用
  • 邏輯刪除欄位的使用方式。
  • 以及整合swagger 自動生成測試介面和 介面說明文件。

碼雲開源

https://gitee.com/mrc1999/mall-pro

持續更新中,歡迎關注