1.需求分析

一个音乐管理系统包括:

1.用户信息管理:该模块主要由管理员进行操作,将所有用户的用户名、密码、邮箱、创建时间以及用户状态列在一张表,管理员可以进行增加、删除(批量删除)、修改以及查询用户信息。

2.歌手信息管理:该模块主要包括歌手的姓名、昵称、性别、歌手照片、歌手出生日期、居住地址、歌手简介。具有增加、删除(逻辑删除、批量删除)、修改以及查询(分页查询、模糊查询)歌手信息等功能。

3:歌曲信息管理:该模块主要包括歌曲名、歌曲简介、发行封面、发行时间、歌词以及更新时间等。有增加、删除(逻辑删除、批量删除)、修改以及查询(分页查询、模糊查询)歌手信息等功能。

4.评论管理:该模块主要包括评论内容、评论类型、是否置顶推荐、评论时间,删除评论等功能。

(还有日志管理,权限管理等功能后续开发)

2.数据库开发

根据上述需求分析进行数据库设计

1.用户信息表   user

用户ididINT
用户名nameVARCHAR(45)
密码passwordVARCHAR(45)
邮箱emailVARCHAR(45)
手机号phoneCHAR(15)
出生日期birthDATETIME
头像avatorVARCHAR(255)
创建时间create_timeDATETIME
更新时间update_timeDATETIME

2.歌手信息表  singer

歌手ididINT
歌手名nameVARCHAR(45)
歌手昵称nickNameVARCHAR(45)
歌手性别sexTINYINT(4)
歌手照片picVARCHAR(255)
出生日期birthDATETIME
居住地址addressVARCHAR(45)
歌手介绍introductionVARCHAR(255)
是否删除is_deleteVARCHAR(20)

3.歌曲信息表  song

歌曲ididINT
歌手idsinger_idINT(10)
歌曲名nameVARCHAR(45)
歌曲简介introductionVARCHAR(255)
发行时间create_timeDATETIME
更新时间update_timeDATETIME
发行照片picVARCHAR(255)
歌词lyricTEXT
歌曲地址urlVARCHAR(255)
是否删除is_deleteVARCHAR(20)

4.歌单表  song_list

歌单ididINT
歌单标题titleVARCHAR(255)
歌单图片picVARCHAR(255)
歌单简介introductionTEXT
歌单风格styleVARCHAR(10)

5. 歌曲歌单关联表

歌单ididINT(10)
歌曲idsong_idINT(10)
歌单idsong_list_idINT(10)

6. 歌曲分类表  song_type

类型ididINT(10)
歌曲idsong_idINT(10)
评论idcomment_idINT(10)
歌曲类型typeVARCHAR(255)

 7.评论表  comment

评论ididINT
用户iduser_idINT(10)
歌曲idsong_idVARCHAR(45)
歌单idsong_list_idINT(10)
评论内容contentVARCHAR(255)
创建时间create_timeDATETIME
是否置顶is_topINT(10)

 具体的sql如下代码所示:没有插入数据,自己随便插入一些就可以

CREATE DATABASE /*!32312 IF NOT EXISTS*/`t_music` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `t_music`;

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL COMMENT '用户名',
  `password` VARCHAR(45) NOT NULL COMMENT '密码',
  `email` VARCHAR(45) COMMENT '邮箱',
  `phone` CHAR(15) DEFAULT NULL COMMENT '手机号',
  `birth` DATETIME DEFAULT NULL COMMENT '出生日期',
  `avator` VARCHAR(255) DEFAULT NULL COMMENT '头像',
  `create_time` DATETIME NOT NULL COMMENT '创建时间',
  `update_time` DATETIME NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT '用户信息表';

DROP TABLE IF EXISTS `singer`;
CREATE TABLE `singer` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL COMMENT '歌手名',
  `nickName` VARCHAR(45)  COMMENT '歌手昵称',
  `sex` TINYINT(4) DEFAULT NULL COMMENT '歌手性别',
  `pic` VARCHAR(255) DEFAULT NULL COMMENT '歌手照片',
  `birth` DATETIME DEFAULT NULL COMMENT '出生日期',
  `address` VARCHAR(45) DEFAULT NULL COMMENT '居住地址',
  `introduction` VARCHAR(255) DEFAULT NULL COMMENT '歌手介绍',
  `is_delete` VARCHAR(20) NOT NULL DEFAULT '0' COMMENT '是否逻辑删除 0-否,1-是',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8 COMMENT '歌手信息表';

DROP TABLE IF EXISTS `song`;
CREATE TABLE `song` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `singer_id` INT(10) UNSIGNED NOT NULL,
  `name` VARCHAR(45) NOT NULL COMMENT '歌曲名',
  `introduction` VARCHAR(255) DEFAULT NULL COMMENT '歌曲简介',
  `create_time` DATETIME NOT NULL COMMENT '发行时间',
  `update_time` DATETIME NOT NULL COMMENT '更新时间',
  `pic` VARCHAR(255) DEFAULT NULL COMMENT '发行照片',
  `lyric` TEXT COMMENT '歌词',
  `url` VARCHAR(255) NOT NULL COMMENT '歌曲地址',
  `is_delete` VARCHAR(20) NOT NULL DEFAULT '0' COMMENT '是否逻辑删除 0-否,1-是',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8 COMMENT '歌曲信息表';

DROP TABLE IF EXISTS `song_list`;
CREATE TABLE `song_list` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(255) NOT NULL COMMENT '歌单标题',
  `pic` VARCHAR(255) DEFAULT NULL COMMENT '歌单图片',
  `introduction` TEXT COMMENT '歌单简介',
  `style` VARCHAR(10) DEFAULT '无' COMMENT '歌单风格',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=42340354 DEFAULT CHARSET=utf8 COMMENT '歌单表';

DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` INT(10) UNSIGNED NOT NULL COMMENT '用户id',
  `song_id` INT(10) UNSIGNED DEFAULT NULL COMMENT '歌曲id',
  `song_list_id` INT(10) UNSIGNED DEFAULT NULL COMMENT '歌单id',
  `content` VARCHAR(255) DEFAULT NULL COMMENT '评论内容',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `is_top` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶  0-未置顶,1-置顶',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `song_list_linked`;
CREATE TABLE `song_list_linked` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `song_id` INT(10) UNSIGNED NOT NULL,
  `song_list_id` INT(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=212 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `song_type`;
CREATE TABLE `song_type` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `song_id` INT(10) UNSIGNED DEFAULT NULL COMMENT '歌曲id',
  `comment_id` INT(10) UNSIGNED DEFAULT NULL COMMENT '评论id',
  `type` VARCHAR(255) DEFAULT NULL COMMENT '歌曲类型',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8 COMMENT '歌曲类型表';

3. 歌手信息后台开发

通过代码生成器,整合swagger进行后台相关开发,主要目录如下

 对应的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>music</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>music</name>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.properties:

# 服务端口
server.port=8888

# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/t_music?serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#mybatis日志
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#mybatis-plus.mapper-locations=classpath:com/example/serviceedu/mapper/xml/*.xml

mybatis-plus.mapper-locations=classpath*:mapper/*.xml,classpath:mapper/**/*Mapper.xml

运行codegenerator代码生成器,生成相应的代码,比如生成singer相关的代码

CodeGenerator代码生成器代码如下:

package com.example.music.config;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {
    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw  new MybatisPlusException("请输入正确的" + tip + "!");
    }
    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        //作者
        gc.setAuthor("mozz");
        //打开输出目录
        gc.setOpen(false);
        //xml开启 BaseResultMap
        gc.setBaseResultMap(true);
        //xml 开启BaseColumnList
        gc.setBaseColumnList(true);
        // 实体属性 Swagger2 注解
        gc.setSwagger2(true);
        mpg.setGlobalConfig(gc);
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/t_music? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia" + "/Shanghai");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);
        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.example.music")
                .setEntity("entity")
                .setMapper("mapper")
                .setService("service")
                .setServiceImpl("service.impl")
                .setController("controller");
        mpg.setPackageInfo(pc);
        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会
                return projectPath + "/src/main/resources/mapper/"
                        + tableInfo.getEntityName() + "Mapper"
                        + StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        //数据库表映射到实体的命名策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        //数据库表字段映射到实体的命名策略
        strategy.setColumnNaming(NamingStrategy.no_change);
        //lombok模型
        strategy.setEntityLombokModel(true);
        //生成 @RestController 控制器
        strategy.setRestControllerStyle(true);
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        //表前缀
//        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

 

文件自动生成,随后进行代码开发,有些是通过mybaits-plus实现的。

整体目录如下:

返回结果接口及封装类

package com.example.music.commons;

public interface ResultCode {

    public static Integer SUCCESS = 20000;
    public static Integer ERROR = 20001;

}
package com.example.music.commons;


//import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

//统一返回结果的类
@Data
public class R {

    @ApiModelProperty(value = "是否成功")
    private boolean success;

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private Map<String,Object> data = new HashMap<String,Object>();

    //构造方法私有
    private R(){};

    //成功的静态方法
    public static R ok(){
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    //失败的静态方法
    public static R error(){
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R data(String s,Object o){
        this.data.put(s,o);
        return this;
    }

    public R data(Map<String,Object> map){
        this.setData(map);
        return this;
    }
}

swagger配置类

package com.example.music.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("API文档")
                .description("接口定义")
                .version("1.0")
                .contact(new Contact("mozz", "http://www.baidu.com",
                        "1345656307@qq.com"))
                .build();
    }
}

异常类

package com.example.music.expection;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MusicExpection extends RuntimeException{

    private Integer code; //状态码

    private String msg; //异常信息

}
package com.example.music.expection;

import com.example.music.commons.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@ControllerAdvice
@Slf4j
public class GlobalExpectionHandler {

    //指定出现什么异常执行这个方法
    @ExceptionHandler(Exception.class)
    @ResponseBody  //为了返回数据
    public R error(Exception e){
        e.printStackTrace();
        return R.error().message("执行了全局异常");
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody  //为了返回数据
    public R error(MethodArgumentTypeMismatchException e){
        e.printStackTrace();
        return R.error().message("执行了MethodArgumentTypeMismatchException异常");
    }

    //自定义异常
    @ExceptionHandler(MusicExpection.class)
    @ResponseBody  //为了返回数据
    public R error(MusicExpection e){
        log.error(e.getMessage());
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

}

实体类

package com.example.music.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Singer对象", description="")
public class Singer implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String name;

    private String nickname;

    private Integer sex;

    private String pic;

    private LocalDateTime birth;

    private String address;

    private String introduction;

    private String isDelete;


}

创建一个vo目录,目录下创建一个PageResult实体,用来对分页进行封装

package com.example.music.vo;

import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(description = "分页对象")
public class PageResult<T> {

    private List<T> records;  //分页列表

    private Integer count; //总数

}

1.controller(包括分页查询,id查询,修改,删除,批量删除等功能)

package com.example.music.controller;


import com.example.music.commons.R;
import com.example.music.entity.Singer;
import com.example.music.service.ISingerService;
import com.example.music.vo.PageResult;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
@RestController
@RequestMapping("/music/singer")
@CrossOrigin
public class SingerController {

    @Autowired
    private ISingerService singerService;


    /**
     * 分页查询所有歌手信息
     * @param page
     * @param limit
     * @return
     */
    @ApiOperation(value = "分页查询所有歌手信息")
    @GetMapping("lists/{page}/{limit}")
    public R getAllSinger(@ApiParam(name = "page", value = "当前页码", required = true)
                          @PathVariable("page") Long page,
                          @ApiParam(name = "limit", value = "每页记录数", required = true)
                          @PathVariable("limit") Long limit){
        page = (page-1)*limit;
        PageResult<Singer> singers = singerService.getAllSinger(page,limit);
        return R.ok().data("data",singers);
    }

    /**
     * 关键字模糊查询
     * @param keyword
     * @return
     */
    @ApiOperation(value = "关键字模糊查询")
    @GetMapping("{keyword}/{page}/{limit}")
    public R getSingerByKeyword(@PathVariable("keyword") String keyword,
                                @PathVariable("page") Long page,
                                @PathVariable("limit") Long limit){
        page = (page-1)*limit;
        PageResult<Singer> singers = singerService.getSingerByKeyword(keyword,page,limit);
        return R.ok().data("data",singers);
    }


    /**
     * 根据id查询歌手信息
     * @param id
     * @return
     */
    @ApiOperation(value = "根据id查询")
    @GetMapping("/info/{id}")
    public R getSingerById(@PathVariable("id") Integer id){
        Singer singer = singerService.getSingerById(id);
        return R.ok().data("data",singer);
    }

    /**
     * 根据id删除歌手信息
     * @param id
     * @return
     */
    @ApiOperation(value = "根据id删除歌手信息")
    @PostMapping("delete/{id}")
    public R deleteSinger(@PathVariable("id") Integer id){
        singerService.deleteSinger(id);
        return R.ok();
    }


    /**
     * 批量删除歌手信息
     * @param ids
     * @return
     */
    @ApiOperation(value = "批量删除歌手信息")
    @PostMapping("/batchDelete/{ids}")
    public R batchDeleteSinger(@PathVariable("ids") Integer[] ids){
        singerService.batchDeleteSinger(ids);
        return R.ok();
    }


    /**
     * 添加歌手信息
     * @param singer
     * @return
     */
    @ApiOperation(value = "添加歌手信息")
    @PostMapping("addSinger")
    public R addSinger(@RequestBody Singer singer){
        singerService.addSinger(singer);
        return R.ok().data("data",singer);
    }


    /**
     * 修改歌手信息
     * @param singer
     * @return
     */
    @ApiOperation(value = "修改歌手信息")
    @PostMapping("update")
    public R updateSinger(@RequestBody Singer singer){
        singerService.updateSinger(singer);
        return R.ok().data("data",singer);
    }


}

 2.service

package com.example.music.service;

import com.example.music.entity.Singer;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.music.vo.PageResult;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
public interface ISingerService extends IService<Singer> {

    PageResult<Singer> getAllSinger(Long page, Long limit);

    PageResult<Singer> getSingerByKeyword(String keyword,Long page, Long limit);

    Singer getSingerById(Integer id);

    void deleteSinger(Integer id);

    void batchDeleteSinger(Integer[] ids);

    void addSinger(Singer singer);

    void updateSinger(Singer singer);
}

实现类

package com.example.music.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.music.entity.Singer;
import com.example.music.expection.MusicExpection;
import com.example.music.mapper.SingerMapper;
import com.example.music.service.ISingerService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.music.vo.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
@Service
public class SingerServiceImpl extends ServiceImpl<SingerMapper, Singer> implements ISingerService {

    @Resource
    private SingerMapper singerMapper;


    @Override
    public PageResult<Singer> getAllSinger(Long page, Long limit) {

        //获取所有歌手信息
        List<Singer> singers = singerMapper.getAllSinger(page,limit);
        PageResult<Singer> pageResult = new PageResult<>();
        pageResult.setRecords(singers);

        //总记录数
        Integer count = singerMapper.selectList(null).size();
        pageResult.setCount(count);
        return pageResult;
    }


    @Override
    public PageResult<Singer> getSingerByKeyword(String keyword,Long page, Long limit) {

        List<Singer> singers = singerMapper.getSingerByKeyword(keyword,page,limit);
        PageResult<Singer> result = new PageResult<>();
        result.setRecords(singers);

        QueryWrapper wrapper = new QueryWrapper();
        wrapper.like("name",keyword);
        Integer count = singerMapper.selectList(wrapper).size();
        result.setCount(count);
        return result;
    }

    @Override
    public Singer getSingerById(Integer id) {
        QueryWrapper<Singer> wrapper = new QueryWrapper();
        wrapper.eq("id",id);
        wrapper.eq("is_delete","0");
        Singer singer = singerMapper.selectOne(wrapper);
        return singer;
    }

    @Override
    public void deleteSinger(Integer id) {
        singerMapper.deleteSinger(id);
    }

    @Override
    public void batchDeleteSinger(Integer[] ids) {
        singerMapper.batchDeleteSinger(ids);
    }

    @Override
    public void addSinger(Singer singer) {
        //判断是否有该歌手信息
        QueryWrapper<Singer> wrapper = new QueryWrapper<>();
        wrapper.eq("is_delete","0");
        List<Singer> singers = singerMapper.selectList(wrapper);
        String name = singer.getName();
        for(Singer item: singers){
            if(name.equals(item.getName())){
                throw new MusicExpection(20000,"该歌手信息已存在");
            }
        }
        singerMapper.insert(singer);
    }

    @Override
    public void updateSinger(Singer singer) {

        QueryWrapper<Singer> wrapper = new QueryWrapper<>();
        wrapper.eq("id",singer.getId());
        singerMapper.update(singer, wrapper);
    }
}

3.mapper

package com.example.music.mapper;

import com.example.music.entity.Singer;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
public interface SingerMapper extends BaseMapper<Singer> {

    List<Singer> getAllSinger(@Param("current") Long page, @Param("size") Long limit);

    void deleteSinger(@Param("id") Integer id);

    void batchDeleteSinger(Integer[] ids);

    List<Singer> getSingerByKeyword(@Param("keyword")String keyword,
                                    @Param("current")Long page,
                                    @Param("size")Long limit);
}

4.xml(有几个sql没用mybatis-plus,mybatis-plus用的不太熟,所以自己写的)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.music.mapper.SingerMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.example.music.entity.Singer">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="sex" property="sex" />
        <result column="pic" property="pic" />
        <result column="birth" property="birth" />
        <result column="location" property="address" />
        <result column="introduction" property="introduction" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, sex, pic, birth, location, introduction
    </sql>

    <select id="getAllSinger" resultType="com.example.music.entity.Singer">
        select `id`,`name`,nickname,sex,pic,birth,address,introduction,is_delete
        from singer where is_delete = '0' order by id ASC limit #{current},#{size}
    </select>

    <select id="getSingerByKeyword" resultType="com.example.music.entity.Singer">
        select `id`,`name`,nickname,sex,pic,birth,address,introduction,is_delete
        from singer where is_delete = '0' and `name` like concat ('%',#{keyword},'%')
        order by id ASC limit #{current},#{size}
    </select>

    <update id="deleteSinger" parameterType="integer">
        update singer set is_delete = '1' where id=#{id}
    </update>

    <update id="batchDeleteSinger">
        update singer t set t.is_delete = '1' where
        t.id in
        <foreach item="ids" collection="array" index="index" open="("
                 separator="," close=")">
            #{ids}
        </foreach>
    </update>

</mapper>

至此整个后台的增删改查全部完成,启动类不要忘记@MapperScan去映射mapper文件

通过swagger进行测试:

 可以看到所有的接口都通过swagger展示出来,去对应的接口中进行测试即可,我这边通过测试都已经成功,接下来就进行前端页面联调开发了。

4.歌手信息前台开发:

本次项目使用的是vue-admin-template模板进行开发,通过nginx进行端口代理,nginx需要修改一下nginx.conf文件中的地址信息。(这个模板我自己改动过)

其中8888就是你yml或者properties文件中设置的端口号,music就是你统一的路径地址。 

首先进行前后端联调,联调成功后具体页面如下:

出现该页面说明前后端联调成功,下面进行具体的开发。

(1)首先左侧菜单应该包含用户管理、歌手管理、歌曲管理以及评论管理

该部分主要通过前端路由实现,可以看到,设置好后页面效果如下:

 随后就可以在对应的页面进行开发了,路由代码如下:

import Vue from 'vue'
import Router from 'vue-router'

// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading

Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

/**
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
*                                if not set alwaysShow, only more than one route under the children
*                                it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect           if `redirect:noredirect` will no redirect in the breadcrumb
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
* meta : {
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar,
  }
**/
export const constantRouterMap = [
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  { path: '/404', component: () => import('@/views/404'), hidden: true },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    hidden: true,
    children: [{
      path: 'dashboard',
      component: () => import('@/views/dashboard/index')
    }]
  },
  
  
  {
     path: '/test',
     component: Layout,
     name: 'test',
	 alwaysShow:true,
	 meta: { title: '用户管理', icon: 'form' },
     children: [
  	   {
  	     path: 'userList',
  	     name: 'userList',
  	     component: () => import('@/views/user/userList'),
  	     meta: { title: '用户列表', icon: 'form' },
  	   }
     ]
   },
  

{
	   path: '/singer',
	   component: Layout,
	   name: 'singer',
	   alwaysShow:true,
	   meta: { title: '歌手管理', icon: 'form' },
	   children: [
		   {
		     path: 'singerList',
		     name: 'singerList',
		     component: () => import('@/views/singer/singerList'),
		     meta: { title: '歌手列表', icon: 'form' },
		   },
		   
	   ]
	 },
	 
	 {
	 	   path: '/song',
	 	   component: Layout,
	 	   name: 'song',
		   alwaysShow:true,
	 	   meta: { title: '歌曲管理', icon: 'form' },
	 	   children: [
	 		   {
	 		     path: 'songList',
	 		     name: 'songList',
	 		     component: () => import('@/views/song/songList'),
	 		     meta: { title: '歌曲列表', icon: 'form' },
	 		   }
	 	   ]
	 	 },
		 
	 {
		   path: '/comment',
		   component: Layout,
		   name: 'comment',
		   alwaysShow:true,
		   meta: { title: '评论管理', icon: 'form' },
		   children: [
			   {
				 path: 'commentList',
				 name: 'commentList',
				 component: () => import('@/views/comment/commentList'),
				 meta: { title: '评论列表', icon: 'form' },
			   }
		   ]
		 },
	
	 
	//  {
	//     path: '/home',
	//     component: Layout,
	//     name: 'home',
	//     meta: { title: '首页', icon: 'form' },
	//     children: []
	//   },




  { path: '*', redirect: '/404', hidden: true }
]

export default new Router({
  // mode: 'history', //后端支持可开
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

然后进入到歌手列表进行页面开发,具体如下

 基础的增删改查就是这些了,然后就是如何从后台获取数据了。

前端请求后端接口全部统一写在了api目录下

 singer.js文件

import request from '@/utils/request'

export default {

    //查询所有歌手
    getAllSingers(current, limit){
      return request({
        url: `/music/singer/lists/${current}/${limit}`,
        method: 'get'
      })
    },

    //批量删除
    batchdeleteSinger(ids){
      return request({
        url:'/music/singer/batchDelete/'+ids,
        method: 'post'
      })
    },
	
	//id删除
	deleteSinger(id){
	  return request({
	    url:'/music/singer/delete/'+id,
	    method: 'post'
	  })
	},

    //根据姓名模糊查询
    getSingerByKeyword(keyword,current, limit){
      return request({
        url: `/music/singer/${keyword}/${current}/${limit}`,
        method: 'get'
      })
    },

//添加歌手
    addSinger(singer) {
        return request({
            url: `/music/singer/addSinger`,
            method: 'post',
            data: singer
        })
    },
	
	// 修改歌手信息
	updateSinger(singer){
	  return request({
	    url:`/music/singer/update`,
	    method: 'post',
	    data: singer
	  })
	},

}

然后在singerList.vue中写页面,如下:

<template>
	<div class="app-container">
		<h3>歌手列表</h3>
		<el-button type="danger" @click="batchdelete"
		:disabled="this.sels.length === 0">批量删除</el-button>
		 <el-button type="primary" >新增</el-button>
		 <el-input
		     style="width:20%;margin-top: 10px;"
		     placeholder="请输入姓名模糊查询"
		     prefix-icon="el-icon-search"
		     clearable
			 v-model="keyword">
		 </el-input>
		  <el-button type="primary" icon="el-icon-search" @click="loadListData()">搜索</el-button>
		<el-table
		    :data="tableData"
			border
			fit
			highlight-current-row
		    style="width: 100%;margin-top: 20px;"
			@selection-change="handleSelectionChange"
			>
			<el-table-column
			      type="selection"
			      border
			      width="55">
			</el-table-column>
		    <el-table-column
		      label="姓名"
		      width="120">
		      <template slot-scope="scope">
		        <span style="margin-left: 10px">{{ scope.row.name }}</span>
		      </template>
		    </el-table-column>
		    <el-table-column
		      label="昵称"
		      width="120">
		      <template slot-scope="scope">
		        <span style="margin-left: 10px">{{ scope.row.nickname }}</span>
		      </template>
		    </el-table-column>
			
			<el-table-column
			  label="性别"
			  width="80">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.sex == 0 ? '女' : '男' }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="出生日期"
			  prop="birth"
			  sortable
			  width="180">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.birth }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="地址"
			  width="120">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.address }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="简介"
			  width="180">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px"><!-- {{ scope.row.introduction }} --></span>
			  </template>
			</el-table-column>
			
		    <el-table-column label="操作">
		      <template slot-scope="scope">
		        <el-button
		          size="mini"
				  type="success" plain
		          @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
		        <el-button
		          size="mini"
		          type="danger"
		          @click="handleDelete(scope.row.id)">删除</el-button>
		      </template>
		    </el-table-column>
		  </el-table>
		  
		  
		 <!-- 分页 -->
		  <div style="text-align: center;margin-top: 10px;">
			 <el-pagination
			       @size-change="handleSizeChange"
			       @current-change="handleCurrentChange"
			       :current-page="page"
			       :page-sizes="[5, 10, 15, 20]"
			       :page-size="limit"
			       layout="total, sizes, prev, pager, next, jumper"
			       :total="total">
			     </el-pagination>
			 
		  </div>
	</div>
	
	
</template>

<script>
	import singer from '@/api/singer/singer'
	
	export default {
	    data() {
	      return {
			sels:[],
			keyword:'',
			
	        tableData: [],
			page: 1,//开始页
			limit: 5, //每页记录数
			total: 0, //总记录数
	      }
	    },
		
		watch:{
		  keyword(newValue){
		    if(!newValue){
		      this.loadListData();
		    }
		  }
		},
		
		created() {
			this.getList();
		},
		
	    methods: {
	      handleEdit(index, row) {
	        console.log(index, row);
	      },
	      handleDelete(id) {
	        this.$confirm('此操作将永久删除,是否继续?','提示',{
	            confirmButtonText:'确定',
	            cancelButtonText: '取消',
	            type:'warning'
	        }).then(()=>{
	        
	           singer.deleteSinger(id).then(res => {
	           	if(res){
	           		this.$message.success("删除成功")
	           		this.getList()
	           	}else{
	           		this.$message.success("删除失败")
	           	}
	           })
	        }).catch(() => {})
	      },
		  
		  // this.manageCheckPlanDetailTableData 表格数据数组
		  handleSelectionChange(sels) {
		       this.sels = sels;
		       // console.log("选中的值",sels.map((item) => item.id));
		  },
		  handleSelectionChange(sels) {
		       this.sels = sels;
		       // console.log("选中的值",sels.map((item) => item.id));
		  },
		  
		  handleCurrentChange(page) {
		    this.page = page;
		    // console.log("page:",this.page)
		    if(this.keyword){
		    	this.searchInfo();
		    }else{
		    	this.getList();
		    }
		  },
		  
		  handleSizeChange(limit){
		    this.limit = limit;
			if(this.keyword){
				this.searchInfo();
			}else{
				this.getList();
			}
		    
		  },
		  
		  loadListData() {
		    if (this.keyword) {
		      // 调用模糊查询接口
		      this.searchInfo();
		    } else{
		      // 调用全部接口
		      this.getList();
		    }
		  },
		  getList(){
		    singer.getAllSingers(this.page,this.limit).then(res => {
		      console.log(res.data.data.records)
		      this.tableData=res.data.data.records
		      this.total=res.data.data.count
		  
		    })
		  },
		  
		  searchInfo(){
		      singer.getSingerByKeyword(this.keyword,this.page,this.limit)
		      .then(res => {
		        console.log(res.data.data);
		        this.tableData=res.data.data.records
		        this.total=res.data.data.count
		      })
		  },
		  
		  batchdelete(){
		          let ids = []
		          this.$confirm('此操作将永久删除,是否继续?','提示',{
		              confirmButtonText:'确定',
		              cancelButtonText: '取消',
		              type:'warning'
		          }).then(()=>{
		              let ids = this.sels.map((item) => item.id);
		              const _this = this
		              singer.batchdeleteSinger(ids).
		              then(function(resp){
		              _this.$message({
		                    type: 'success',
		                    message: '删除成功!'
		                });
						_this.getList()
		               
		              })
		          }).catch(() => {})
		      },
		  
	    }
	  }
	
</script>

<style>
</style>

这时可以看到基本的信息功能差不多完成了。

 还差一个新增和编辑,通过弹框实现,继续开发。

新增和编辑功能完成,整个的前端页面代码如下:

<template>
	<div class="app-container">
		<h3>歌手列表</h3>
		<el-button type="danger" @click="batchdelete"
		:disabled="this.sels.length === 0">批量删除</el-button>
		 <el-button type="primary" @click="addSinger">新增</el-button>
		 <el-input
		     style="width:20%;margin-top: 10px;"
		     placeholder="请输入姓名模糊查询"
		     prefix-icon="el-icon-search"
		     clearable
			 v-model="keyword">
		 </el-input>
		  <el-button type="primary" icon="el-icon-search" @click="loadListData()">搜索</el-button>
		<el-table
		    :data="tableData"
			border
			fit
			highlight-current-row
		    style="width: 100%;margin-top: 20px;"
			@selection-change="handleSelectionChange"
			>
			<el-table-column
			      type="selection"
			      border
			      width="55">
			</el-table-column>
		    <el-table-column
		      label="姓名"
		      width="120">
		      <template slot-scope="scope">
		        <span style="margin-left: 10px">{{ scope.row.name }}</span>
		      </template>
		    </el-table-column>
		    <el-table-column
		      label="昵称"
		      width="120">
		      <template slot-scope="scope">
		        <span style="margin-left: 10px">{{ scope.row.nickname }}</span>
		      </template>
		    </el-table-column>
			
			<el-table-column
			  label="性别"
			  width="80">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.sex == 0 ? '女' : '男' }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="出生日期"
			  prop="birth"
			  sortable
			  width="180">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.birth }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="地址"
			  width="120">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px">{{ scope.row.address }}</span>
			  </template>
			</el-table-column>
			
			<el-table-column
			  label="简介"
			  width="180">
			  <template slot-scope="scope">
			    <span style="margin-left: 10px"><!-- {{ scope.row.introduction }} --></span>
			  </template>
			</el-table-column>
			
		    <el-table-column label="操作">
		      <template slot-scope="scope">
		        <el-button
		          size="mini"
				  type="success" plain
		          @click="handleEdit(scope.row.id)">编辑</el-button>
		        <el-button
		          size="mini"
		          type="danger"
		          @click="handleDelete(scope.row.id)">删除</el-button>
		      </template>
		    </el-table-column>
		  </el-table>
		  
		  
		 <!-- 分页 -->
		  <div style="text-align: center;margin-top: 10px;">
			 <el-pagination
			       @size-change="handleSizeChange"
			       @current-change="handleCurrentChange"
			       :current-page="page"
			       :page-sizes="[5, 10, 15, 20]"
			       :page-size="limit"
			       layout="total, sizes, prev, pager, next, jumper"
			       :total="total">
			     </el-pagination>
			 
		  </div>
		  
		  
		  <el-dialog title="歌手信息" :visible.sync="dialogTableVisible" >
		    <el-form :model="singer" :rules="rules" ref="singer">
		        <el-form-item label="姓名" :label-width="formLabelWidth" prop="name">
		          <el-input v-model="singer.name"></el-input>
		        </el-form-item>
				<el-form-item label="昵称" :label-width="formLabelWidth" prop="sex">
				  <el-input v-model="singer.nickname" autocomplete="off"></el-input>
				</el-form-item>
				<el-form-item label="性别" :label-width="formLabelWidth">
				  <!-- <el-input v-model="singer.sex" autocomplete="off"></el-input> -->
				  <el-radio-group v-model="singer.sex">
				     
				     <el-radio :label="1">男</el-radio>
					 <el-radio :label="0">女</el-radio>
				  </el-radio-group>
				</el-form-item>
				<el-form-item label="出生日期" :label-width="formLabelWidth" prop="birth">
				  <el-input v-model="singer.birth" autocomplete="off"></el-input>
				</el-form-item>
				<el-form-item label="地址" :label-width="formLabelWidth">
				  <el-input v-model="singer.address" autocomplete="off"></el-input>
				</el-form-item>
		        <el-form-item label="简介" :label-width="formLabelWidth">
		         <el-input v-model="singer.introduction" type="textarea" autocomplete="off">
					 
				 </el-input>
		        </el-form-item>
		      </el-form>
			
			 <div slot="footer" class="dialog-footer">
			    <el-button @click="dialogTableVisible = false">取 消</el-button>
			    <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
			  </div>
			
		  </el-dialog>
		  
		  
		  
	</div>
	
	
</template>

<script>
	import singer from '@/api/singer/singer'
	
	export default {
	    data() {

	      return {
			id:'',  
			singer:{
				id:'',
				name:'',
				nickname:'',
				sex:'',
				birth:'',
				address:'',
				introduction:''
			},
			sels:[],
			keyword:'',
			dialogTableVisible:false,
			dialogFormVisible:false,
	        tableData: [],
			page: 1,//开始页
			limit: 5, //每页记录数
			total: 0, //总记录数
			formLabelWidth: '100px',
			 rules: {
			  name: [
				{ required: true, message: '请输入姓名', trigger: 'blur' }
			  ],
			  sex: [
			  		{ required: true, message: '性别不能为空', trigger: 'blur' }
			  ],
			  birth: [
			  		{ required: true, message: '出生日期不能为空', trigger: 'blur' }
			  ]
			}
	      }
	    },
		
		watch:{
		  keyword(newValue){
		    if(!newValue){
		      this.loadListData();
		    }
		  }
		},
		
		created() {
			this.getList();
		},
		
	    methods: {
		  addSinger(){
			  this.dialogTableVisible=true;
			  this.singer.name = '';
			  this.singer.nickname = '';
			  this.singer.sex = '';
			  this.singer.birth='';
			  this.singer.address='';
			  this.singer.introduction='';
		  },
	      handleEdit(id) {
			this.dialogTableVisible=true;
			singer.getSingerById(id).then(res => {
				console.log("id:",res.data.data)
				this.singer = res.data.data
			})
	      },
	      handleDelete(id) {
	        this.$confirm('此操作将永久删除,是否继续?','提示',{
	            confirmButtonText:'确定',
	            cancelButtonText: '取消',
	            type:'warning'
	        }).then(()=>{
	        
	           singer.deleteSinger(id).then(res => {
	           	if(res){
	           		this.$message.success("删除成功")
	           		this.getList()
	           	}else{
	           		this.$message.success("删除失败")
	           	}
	           })
	        }).catch(() => {})
	      },
		  
		  // this.manageCheckPlanDetailTableData 表格数据数组
		  handleSelectionChange(sels) {
		       this.sels = sels;
		       // console.log("选中的值",sels.map((item) => item.id));
		  },
		  handleSelectionChange(sels) {
		       this.sels = sels;
		       // console.log("选中的值",sels.map((item) => item.id));
		  },
		  
		  handleCurrentChange(page) {
		    this.page = page;
		    // console.log("page:",this.page)
		    if(this.keyword){
		    	this.searchInfo();
		    }else{
		    	this.getList();
		    }
		  },
		  
		  handleSizeChange(limit){
		    this.limit = limit;
			if(this.keyword){
				this.searchInfo();
			}else{
				this.getList();
			}
		    
		  },
		  
		  loadListData() {
		    if (this.keyword) {
		      // 调用模糊查询接口
		      this.searchInfo();
		    } else{
		      // 调用全部接口
		      this.getList();
		    }
		  },
		  getList(){
		    singer.getAllSingers(this.page,this.limit).then(res => {
		      console.log(res.data.data.records)
		      this.tableData=res.data.data.records
		      this.total=res.data.data.count
		  
		    })
		  },
		  
		  searchInfo(){
		      singer.getSingerByKeyword(this.keyword,this.page,this.limit)
		      .then(res => {
		        console.log(res.data.data);
		        this.tableData=res.data.data.records
		        this.total=res.data.data.count
		      })
		  },
		  
		  batchdelete(){
		          let ids = []
		          this.$confirm('此操作将永久删除,是否继续?','提示',{
		              confirmButtonText:'确定',
		              cancelButtonText: '取消',
		              type:'warning'
		          }).then(()=>{
		              let ids = this.sels.map((item) => item.id);
		              const _this = this
		              singer.batchdeleteSinger(ids).
		              then(function(resp){
		              _this.$message({
		                    type: 'success',
		                    message: '删除成功!'
		                });
						_this.getList()
		               
		              })
		          }).catch(() => {})
		      },
		  
		saveSinger(){

			this.singer.id = this.id;
			singer.addSinger(this.singer).then(res => {
				console.log("data",res.data.data)
				this.dialogTableVisible=false;
				this.$message({
				     type: 'success',
				     message: '添加歌手成功!'
				   });
				   this.getList();
				})
		   },
		   updateSinger(){
			   singer.updateSinger(this.singer).then(res => {
				   this.dialogTableVisible = false;
				   this.$message({
				        type: 'success',
				        message: '修改歌手信息成功!'
				      });
				      this.getList()
			   })
		   },
		   
		   saveOrUpdate(){
			   if(!this.singer.id){
			     this.saveSinger()
			   }else{
			     this.updateSinger()
			   }
		   }
		
	    }
	  }
	
</script>

<style>
</style>

至此整个的springboot基础的前后端增删改查就完成了。

继续更:

由于增删改查完成,涉及到表单功能,比如新增与删除功能,需要对其进行一系列的校验,这边前端是通过设置rules规则完成校验,必要时配合正则表达式。同样后端也需要进行校验,后端采用JSR303校验

 

JSR303校验

首先引入以下依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>

 然后在controller层需要添加@Valid注解,并且传入BindingResult,比如修改歌手信息,歌手名不能为空,在实体类上添加相应的注解

package com.example.music.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * <p>
 * 
 * </p>
 *
 * @author mozz
 * @since 2022-09-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="Singer对象", description="")
public class Singer implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @NotBlank(message = "用户名不能为空")
    private String name;

    private String nickname;

    @NotNull
    private Integer sex;

    private String pic;

    @JsonFormat(pattern = "yyyy-MM-dd")
    @NotNull(message = "用户出生日期不能为空")
    private Date birth;

    private String address;

    private String introduction;

    private String isDelete;


}

controller中:

 /**
     * 修改歌手信息
     * @param singer
     * @return
     */
    @ApiOperation(value = "修改歌手信息")
    @PostMapping("update")
    public R updateSinger(@RequestBody @Valid Singer singer, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            result.getFieldErrors().forEach((item) -> {
                String message = item.getDefaultMessage();
                String field = item.getField();
                map.put(field,message);
            });
            return R.error().data("data",map);
        }
        singerService.updateSinger(singer);
        return R.ok().data("data",singer);
    }

 最后运行swagger验证:让姓名name为空查看结果

 

可以看到,返回的信息就是我们实体类上注解对应的message信息,同样的增加功能也需要进行对用的校验操作。至此校验完成。

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐