V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
songboriceboy
V2EX  ›  程序员

SpringBoot+Vue 豆宝社区前后端分离项目手把手实战系列教程 02---创建后端工程

  •  
  •   songboriceboy · 2021-02-19 22:05:24 +08:00 · 1169 次点击
    这是一个创建于 1408 天前的主题,其中的信息可能已经有所发展或是发生改变。

    豆宝社区项目实战教程简介

    本项目实战教程配有免费视频教程,配套代码完全开源。手把手从零开始搭建一个目前应用最广泛的 Springboot+Vue 前后端分离多用户社区项目。本项目难度适中,为便于大家学习,每一集视频教程对应在 Github 上的每一次提交。

    项目首页截图

    image

    代码开源地址

    前端 后端

    视频教程地址

    视频教程

    前端技术栈

    Vue Vuex Vue Router Axios Bulma Buefy Element Vditor DarkReader

    后端技术栈

    Spring Boot Mysql Mybatis MyBatis-Plus Spring Security JWT Lombok

    搭建后端工程

    0.导入 sql

    在数据库导入

    /*
     Navicat Premium Data Transfer
     Source Server         : localhost
     Source Server Type    : MySQL
     Source Server Version : 80022
     Source Host           : localhost:3306
     Source Schema         : doubao
     Target Server Type    : MySQL
     Target Server Version : 80022
     File Encoding         : 65001
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for bms_billboard
    -- ----------------------------
    DROP TABLE IF EXISTS `bms_billboard`;
    CREATE TABLE `bms_billboard`  (
      `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
      `content` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '公告',
      `create_time` datetime NULL DEFAULT NULL COMMENT '公告时间',
      `show` tinyint(1) NULL DEFAULT NULL COMMENT '1:展示中,0:过期',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4  COMMENT = '全站公告' ROW_FORMAT = DYNAMIC;
    
    -- ----------------------------
    -- Records of bms_billboard
    -- ----------------------------
    INSERT INTO `bms_billboard` VALUES (2, 'R1.0 开始已实现护眼模式 ,妈妈再也不用担心我的眼睛了。', '2020-11-19 17:16:19', 0);
    INSERT INTO `bms_billboard` VALUES (4, '系统已更新至最新版 1.0.1', NULL, 1);
    
    
    -- ----------------------------
    -- Table structure for bms_follow
    -- ----------------------------
    DROP TABLE IF EXISTS `bms_follow`;
    CREATE TABLE `bms_follow`  (
      `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
      `parent_id` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '被关注人 ID',
      `follower_id` varchar(20) CHARACTER SET utf8mb4 NULL DEFAULT NULL COMMENT '关注人 ID',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 130 CHARACTER SET = utf8mb4  COMMENT = '用户关注' ROW_FORMAT = DYNAMIC;
    
    -- ----------------------------
    -- Records of bms_follow
    -- ----------------------------
    INSERT INTO `bms_follow` VALUES (65, '1329723594994229250', '1317498859501797378');
    INSERT INTO `bms_follow` VALUES (85, '1332912847614083073', '1332636310897664002');
    INSERT INTO `bms_follow` VALUES (129, '1349290158897311745', '1349618748226658305');
    
    -- ----------------------------
    -- Table structure for bms_post
    -- ----------------------------
    DROP TABLE IF EXISTS `bms_post`;
    CREATE TABLE `bms_post`  (
      `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
      `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '标题',
      `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 'markdown 内容',
      `user_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作者 ID',
      `comments` int NOT NULL DEFAULT 0 COMMENT '评论统计',
      `collects` int NOT NULL DEFAULT 0 COMMENT '收藏统计',
      `view` int NOT NULL DEFAULT 0 COMMENT '浏览统计',
      `top` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否置顶,1-是,0-否',
      `essence` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否加精,1-是,0-否',
      `section_id` int NULL DEFAULT 0 COMMENT '专栏 ID',
      `create_time` datetime NOT NULL COMMENT '发布时间',
      `modify_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
      UNIQUE INDEX `title`(`title`) USING BTREE,
      INDEX `user_id`(`user_id`) USING BTREE,
      INDEX `create_time`(`create_time`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '话题表' ROW_FORMAT = DYNAMIC;
    
    -- ----------------------------
    -- Table structure for bms_comment
    -- ----------------------------
    DROP TABLE IF EXISTS `bms_comment`;
    CREATE TABLE `bms_comment`  (
      `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
      `content` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '内容',
      `user_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作者 ID',
      `topic_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'topic_id',
      `create_time` datetime NOT NULL COMMENT '发布时间',
      `modify_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '评论表' ROW_FORMAT = DYNAMIC;
    
    -- ----------------------------
    -- Table structure for ums_user
    -- ----------------------------
    DROP TABLE IF EXISTS `ums_user`;
    CREATE TABLE `ums_user`  (
      `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户 ID',
      `username` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
      `alias` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
      `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '密码',
      `avatar` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
      `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
      `mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机',
      `score` int NOT NULL DEFAULT 0 COMMENT '积分',
      `token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT 'token',
      `bio` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '个人简介',
      `active` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否激活,1:是,0:否',
      `status` bit(1) NULL DEFAULT b'1' COMMENT '状态,1:使用,0:停用',
      `role_id` int NULL DEFAULT NULL COMMENT '用户角色',
      `create_time` datetime NOT NULL COMMENT '加入时间',
      `modify_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
      PRIMARY KEY (`id`) USING BTREE,
      UNIQUE INDEX `user_name`(`username`) USING BTREE,
      INDEX `user_email`(`email`) USING BTREE,
      INDEX `user_create_time`(`create_time`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
    
    -- ----------------------------
    -- Records of ums_user
    -- ----------------------------
    INSERT INTO `ums_user` VALUES ('1349290158897311745', 'admin', 'admin', '$2a$10$8qx711TBg/2hxfL7N.sxf.0ROMhR/iuPhQx33IFqGd7PLgt5nGJTO', 'https://s3.ax1x.com/2020/12/01/DfHNo4.jpg', '[email protected]', NULL, 2, '', '自由职业者', b'1', b'1', NULL, '2021-01-13 17:40:17', NULL);
    INSERT INTO `ums_user` VALUES ('1349618748226658305', 'zhangsan', 'zhangsan', '$2a$10$7K3yYv8sMV5Xsc2facXTcuyDo8JQ4FJHvjZ7qtWYcJdei3Q6Fvqdm', 'https://s3.ax1x.com/2020/12/01/DfHNo4.jpg', '[email protected]', NULL, 0, '', '自由职业者', b'1', b'1', NULL, '2021-01-14 15:25:59', NULL);
    
    SET FOREIGN_KEY_CHECKS = 1;
    

    创建 maven 工程

    1.pom 依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mybatis-plus.version>3.4.2</mybatis-plus.version>
        <fastjson.version>1.2.75</fastjson.version>
        <hutool.version>5.5.7</hutool.version>
        <jwt.version>0.9.1</jwt.version>
        <emoji-java.version>5.1.1</emoji-java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--jjwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <!--emoji-java 表情包-->
        <dependency>
            <groupId>com.vdurmont</groupId>
            <artifactId>emoji-java</artifactId>
            <version>${emoji-java.version}</version>
        </dependency>
        <!-- lettuce pool 缓存连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--HuTool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!--yaml-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--bean validator-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    

    2.yam 配置

    # 端口号
    server:
      port: 8081
      
    # 数据库
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
        url: jdbc:mysql://127.0.0.1:3306/nodepad_mblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=GMT%2B8
        type: com.zaxxer.hikari.HikariDataSource
    
    # sql 日志打印
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    3.启动类

    // 我们这里还没有配置数据库,exclude = {DataSourceAutoConfiguration.class 就是启动时不加载数据库
    // @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    @Slf4j
    @SpringBootApplication
    public class BlogApplication {
        
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(BlogApplication.class, args);
            String port = context.getEnvironment().getProperty("server.port");
            log.info("http://localhost:"+port);
        }
    }
    

    启动项目测试有没有报错

    4.项目返回和错误处理

    image-20210211175503499

    4.1api 数据返回

    IErrorCode
    public interface IErrorCode {
        /**
         * 错误编码: -1 失败;200 成功
         *
         * @return 错误编码
         */
        Integer getCode();
    
        /**
         * 错误描述
         *
         * @return 错误描述
         */
        String getMessage();
    }
    
    ApiErrorCode
    public enum ApiErrorCode implements IErrorCode {
    
        /**
         * 成功
         */
        SUCCESS(200, "操作成功"),
        /**
         * 失败
         */
        FAILED(-1, "操作失败"),
        /**
         * 未登录,Token 过期
         */
        UNAUTHORIZED(401, "暂未登录或 token 已经过期"),
        /**
         * 权限不足
         */
        FORBIDDEN(403, "权限不足"),
        /**
         * 参数校验错误
         */
        VALIDATE_FAILED(404, "参数检验失败");
    
        private final Integer code;
        private final String message;
    
        ApiErrorCode(int code, String message) {
            this.code = code;
            this.message = message;
        }
    
        @Override
        public Integer getCode() {
            return code;
        }
    
        @Override
        public String getMessage() {
            return message;
        }
    
        @Override
        public String toString() {
            return "ApiErrorCode{" +
                    "code=" + code +
                    ", message='" +message + '\'' +
                    '}';
        }
    }
    
    ApiResult
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    import java.util.Optional;
    
    
    @Data
    @NoArgsConstructor
    public class ApiResult<T> implements Serializable {
    
        private static final long serialVersionUID = -4153430394359594346L;
        /**
         * 业务状态码
         */
        private long code;
        /**
         * 结果集
         */
        private T data;
        /**
         * 接口描述
         */
        private String message;
    
        /**
         * 全参
         *
         * @param code    业务状态码
         * @param message 描述
         * @param data    结果集
         */
        public ApiResult(long code, String message, T data) {
            this.code = code;
            this.message = message;
            this.data = data;
        }
    
        public ApiResult(IErrorCode errorCode) {
            errorCode = Optional.ofNullable(errorCode).orElse(ApiErrorCode.FAILED);
            this.code = errorCode.getCode();
            this.message = errorCode.getMessage();
        }
    
        /**
         * 成功
         *
         * @param data 结果集
         * @return {code:200,message:操作成功,data:自定义}
         */
        public static <T> ApiResult<T> success() {
            return new ApiResult<T>(ApiErrorCode.SUCCESS.getCode(), ApiErrorCode.SUCCESS.getMessage(), null);
        }
    
        /**
         * 成功
         *
         * @param data 结果集
         * @return {code:200,message:操作成功,data:自定义}
         */
        public static <T> ApiResult<T> success(T data) {
            return new ApiResult<T>(ApiErrorCode.SUCCESS.getCode(), ApiErrorCode.SUCCESS.getMessage(), data);
        }
    
        /**
         * 成功
         *
         * @param data    结果集
         * @param message 自定义提示信息
         * @return {code:200,message:自定义,data:自定义}
         */
        public static <T> ApiResult<T> success(T data, String message) {
            return new ApiResult<T>(ApiErrorCode.SUCCESS.getCode(), message, data);
        }
    
        /**
         * 失败返回结果
         */
        public static <T> ApiResult<T> failed() {
            return failed(ApiErrorCode.FAILED);
        }
    
        /**
         * 失败返回结果
         *
         * @param message 提示信息
         * @return {code:枚举 ApiErrorCode 取,message:自定义,data:null}
         */
        public static <T> ApiResult<T> failed(String message) {
            return new ApiResult<T>(ApiErrorCode.FAILED.getCode(), message, null);
        }
    
        /**
         * 失败
         *
         * @param errorCode 错误码
         * @return {code:封装接口取,message:封装接口取,data:null}
         */
        public static <T> ApiResult<T> failed(IErrorCode errorCode) {
            return new ApiResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
        }
    
        /**
         * 失败返回结果
         *
         * @param errorCode 错误码
         * @param message   错误信息
         * @return {code:枚举 ApiErrorCode 取,message:自定义,data:null}
         */
        public static <T> ApiResult<T> failed(IErrorCode errorCode, String message) {
            return new ApiResult<T>(errorCode.getCode(), message, null);
        }
    
        /**
         * 参数验证失败返回结果
         */
        public static <T> ApiResult<T> validateFailed() {
            return failed(ApiErrorCode.VALIDATE_FAILED);
        }
    
        /**
         * 参数验证失败返回结果
         *
         * @param message 提示信息
         */
        public static <T> ApiResult<T> validateFailed(String message) {
            return new ApiResult<T>(ApiErrorCode.VALIDATE_FAILED.getCode(), message, null);
        }
    
        /**
         * 未登录返回结果
         */
        public static <T> ApiResult<T> unauthorized(T data) {
            return new ApiResult<T>(ApiErrorCode.UNAUTHORIZED.getCode(), ApiErrorCode.UNAUTHORIZED.getMessage(), data);
        }
    
        /**
         * 未授权返回结果
         */
        public static <T> ApiResult<T> forbidden(T data) {
            return new ApiResult<T>(ApiErrorCode.FORBIDDEN.getCode(), ApiErrorCode.FORBIDDEN.getMessage(), data);
        }
    }
    

    4.2 全局异常

    ApiException
    public class ApiException extends RuntimeException {
        private IErrorCode errorCode;
    
        public ApiException(IErrorCode errorCode) {
            super(errorCode.getMessage());
            this.errorCode = errorCode;
        }
    
        public ApiException(String message) {
            super(message);
        }
    
        public IErrorCode getErrorCode() {
            return errorCode;
        }
    }
    
    ApiAsserts
    public class ApiAsserts {
        /**
         * 抛失败异常
         *
         * @param message 说明
         */
        public static void fail(String message) {
            throw new ApiException(message);
        }
    
        /**
         * 抛失败异常
         *
         * @param errorCode 状态码
         */
        public static void fail(IErrorCode errorCode) {
            throw new ApiException(errorCode);
        }
    }
    
    GlobalExceptionHandler
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.util.Map;
    
    @ControllerAdvice
    public class GlobalExceptionHandler {
        /**
         * 捕获自定义异常
         */
        @ResponseBody
        @ExceptionHandler(value = ApiException.class)
        public ApiResult<Map<String, Object>> handle(ApiException e) {
            if (e.getErrorCode() != null) {
                return ApiResult.failed(e.getErrorCode());
            }
            return ApiResult.failed(e.getMessage());
        }
    }
    

    1 条回复    2021-02-22 10:06:54 +08:00
    seven123
        1
    seven123  
       2021-02-22 10:06:54 +08:00
    不错,感谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2472 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 02:44 · PVG 10:44 · LAX 18:44 · JFK 21:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.