您好,欢迎来到叨叨游戏网。
搜索
您的当前位置:首页spring boot 前后端分离项目(商城项目)学习笔记

spring boot 前后端分离项目(商城项目)学习笔记

来源:叨叨游戏网

spring boot 前后端分离项目(商城项目)学习笔记

代码托管平台:gitee

后端代码:

前端代码:

后端配置

springboot项目 pom.xml文件

1.pom文件 阿里云与原生

​ 原生:标签 阿里云用 以下代替 其中pom的pom指其实个父标签。

​ parent标签的作用: 定义当前SpringBoot所有依赖的版本号

​ build标签 springboot项目在打包部署发布时,需要依赖maven工具API

    <dependencyManagement>
         <!--相当于继承了一个父级 -->
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                 <!--标识其是个父级 -->
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.pom标签说明

 <!--项目组ID -->
	<groupId>com.example</groupId>
 <!--项目 -->
    <artifactId>springboot-shiro</artifactId>
 <!--项目版本号 -->
    <version>0.0.1-SNAPSHOT</version>
 <!--项目名 -->
    <name>springboot-shiro</name>
 <!--项目描述 -->
    <description>Demo project for Spring Boot</description>
	 <!--
	<packaging>jar</packaging> 
-->

3.dependency标签

​ 主要用来加载外部依赖

<dependencies> 
<dependency>
            <groupId>org.springframework.boot</groupId>
    <!--spring boot 启动项
		思想:“开箱即用”
		说明:只需要简单引入jar包,简单配置,就可以使用相应功能。
-->
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
</dependencies>
maven

​ maven的jar包查询网址:

例如 A项目依赖 b.jar,b.jar依赖c.jar 所以依赖b,jar相当于依赖了b,jar,c.jar.

  1. Maven打包方式:
  • 1.默认是jar
  • 2.web项目可以打war
  • 3.如果该项目是父级,pom
  1. Maven 依赖的传递性实现原理:

    步骤:

    • 当maven开始解析pom.xml文件,根据依赖坐标,找到指定的jar文件,添加该依赖

    • 然后根据相应jar包的pom文件,进行扫描

    • 扫描文件中的dependent

    • 根据dependent坐标重复上述操作

  2. 文件传输有效性(以上图)

    保证通过jar包镜像导入依赖没被修改,被植入木马

    需求:网络数据传输,一般都需进行数据加密 ,maven传输一般采用SHA1数字签名

    加密算法,保证数据传输有效性。

    一般数据传输会将数据进行SHA1数字加密 生成相应hashcode 与传输好的数据生成的hashcode进行比较,相同才有效

配置文件

1.porperties文件

​ 数据类型: key=value 注意不要有空格

​ 编码: 默认采用 ISO-8859-1编码

spring.datasource.username=root

2.yaml文件

​ 数据类型:key :(空格)value 注意缩进

​ 有层级效果 默认采用utf-8 可以写中文

spring:
  datasource:
    username : root
    password: 123456
    url : jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
    
msg:
	hello: “下雨”
  1. 配置环境切换

    可以在原有yaml的文件下新建 application-dev.yaml,application-test.yaml。。。等,跟相应的后缀,在其中进行相应配置,而在application.yaml 定义所要应用的环境

    (properties相同,但要注意格式)

    #spring.profiles.active=dev    properties的后面跟所要应用application-的后缀
    #指定默认的环境
    spring:
      profiles:
        active: test
    
    

    也可以通过分割符进行配置

    #指定环境默认匹配值---配置要启动的环境
    spring:
    	profiles:
    		active: dev
    ---
    server:
    	port: 9090
    	#为环境定义名称
    spring:
    	config:
    		activate:
    			on-profile: dev
    ---
    #采用---实现环境分割
    spring:
    	config:
    		activate:
    			on-profile: test
    server:
    	port: 8080
    			
    
后端
  1. 注意 我们缩写的dao包,controller包。。。。应该和主启动类处于同一包下,否则无法内识别

  2. 常用注解

 /**
 @Controller
 	1. 将该类交给spring容器管理
 	2. @Controller 注解,在对应的方法上,视图解析器可以解析return 的jsp,html页面,并且跳转到相应页面
 	@ResponseBody  将数据转化为JSON字符串
 	@restCOntroller  相当于@Controller+@ResponseBody
 */
@RestController  //将该类交给Spring管理
@PropertySource(value="classpath:/demo.yaml",encoding = "UTF-8")
//当定义的yaml配合文件不是默认名application时,可以通过该注解获取yaml的地址,以
//及定义其编码格式
public class HelloController {

    /**
     * 规则:
     *    1. 当Spring容器启动时,会加载YML配置文件.
     *       会将内部的key-value结构 加载到spring维护的内存空间中
     *    2. @Value功能,从spring容器中根据key 动态赋值
     *    3. ${key}  spring提供的springel表达式 简称:spel表达式
          语法 从spring容器内部动态赋值
     *
     * 使用场景:
     *      如果代码中需要给成员变量赋值时,一般采用动态赋值的方式.
     */
    @Value("${msg.hello}")
    private String msg;

/**
	 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求
	 当接收到相应的地址请求,执行其下的方法
	 默认 value=“URL地址”
	 还可通过Method定义传参方法 get  post等
	 @GetMapper 功能相同,传参方法为get
	 @PostMapper 功能相同,传参方法为post
*/
    @RequestMapping("/hello")
    public String hello(){

        return msg;
    }
}
@SpringBootApplication		//主启动类的标记注解
public class SpringBootApplication {
 //args是jvm进行参数传递的默认参数
 public static void main(String[] args) {
     SpringApplication.run(SpringBootApplication.class, args);
 }
}

3 .热部署

3.1 我们在开发中反复修改类、页面等资源,每次修改后都是需要重新启动才生效,这样每次启动都会浪费大量时间,我们可以在修改代码后不重启就生效,在pom.xml中添加如下配置就可以实现这样的功能,我们称之为热部署

<!--
 在配置里添加依赖
 支持热部署 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
</dependency>

​ 3.2 配置后也有可能不生效:原因idea默认下不会自动编译,开启后重启idea即可

​ 测试效果:

​ 修改类–>保存:应用会重启

​ 修改配置文件–>保存:应用会重启

​ 修改页面–>保存:应用不会重启,但会重新加载,页面会刷新

4 Lombok插件

作用:动态生成常见 get/set/toString/equals/hashcode构造等在pojo类的方法

  1. 1 试用其要先在idea加载其插件

    4.2 添加jar

<!--引入插件lombok 自动的set/get/构造方法插件  -->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//@Api(注释)   能够对swagger进行相应注释
@Data    //自动添加相对应的 get、set。tostring等方法
@AllArgsConstructor  //生成所有的有参构造
@NoArgsConstructor	//生成无参构造  因为写了有参会将系统不会生成默认无参,因此要自己写
@Accessors(chain = true)//开启链式编程  重写set方法
@ApiModel("用户实体类")//swagger 注解提供有关swagger模型的其它信息,类将在操作中用作类型时自动内省
public class User implements Serializable{//序列化
 @ApiModelProperty("姓名")
 private String name;
 @ApiModelProperty("密码")
 private String password;
 /*  public DemoUser setName(String name){
     this.name=name;
     return this;
 }
 public DemoUser setPassword(String password){
     this.password=password;
     return this;
 }
 //可以链式编程 必须写上述set方法
//Accessors(chain = true) 帮我们写了set方法
 */
//test
 public void test(){
     DemoUser demoUser=new DemoUser();
     demoUser.setPassword(10).setName(10);
 }
}

数据库

以下数据库表结构

item 商品主表 item_cat 商品分类表 item_desc 商品描述表

​ item表是商品的基本信息,tem_desc 表为商品详细数据,用户一般查询时都是查询基本信息。之后对具体的信息进行点击查询商品详细信息,为提高查询效率,将商品表分为item,item_desc 。一个商品和一个详情相互对应 item_desc .id = item.id id;

report 商品数量表

rights 权限表 role 角色表 role_rights 角色权限对应表

user 用户表 user_role 用户对应角色表

项目业务实现流程

  1. 创建spring boot项目

  2. 编辑pom.xml文件

    导入要用到的依赖

     		<dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jdbc</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </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.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.2.0</version>
         </dependency>
    
  3. 配置application.yaml

    配置数据库链接 mybatis配置

设置端口号
server:
  port: 8091
#开启日志调试
# 开启查询日志
logging:
  level:
    com.nuc.ssm.mapper: debug
    #web: trace

#设置数据库链接
spring:
  datasource:
  # 要改对应数据库名
    url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true
    # 要改对应名  码
    username: 用户名
    password: 密码
    driver-class-name: com.mysql.cj.jdbc.Driver #数据源
#配置mybatis  若用MP只用 改为 mybatis-plus其他不变
mybatis:
  #指定别名包----要对应,如有变化,也要修改
  mapper-locations: classpath:/mappers/*.xml
  # 要改对应路径
  type-aliases-package: pojo对应路径
  configuration:
    #开启驼峰映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   ```
  1. 按照数据库建立对应pojo类

  2. 导入前端vue框架项目

  3. 在命令指令框 vue ui 启动框架

1.实现用户登录操作

1.1 用户登录验证接口

1.2 业务逻辑

  1. 用户输入用户名和密码 admin123/admin123456 点击登录按钮
  2. 通过vue中的 axios 发起post请求 /user/login,实现用户登录
  3. 在UserController中接收用户的请求和参数(json).
  4. 先将密码进行加密处理(SHA1,MD5,MD5HASH),根据参数查询数据库.
    4.1 查询成功有数据 输入正确 有且只有一个数据
    4.2 查询失败没有数据 输入有误.
    如果登录成功,则返回token的秘钥(安全性), 利用SysResult对象返回

1.3 实现

定义vo包下的SysResult类

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {
    private Integer status;//200正常  201失败
    private String msg;   //服务器返回的提示信息
    private Object data;  //服务器返回的业务数据
    /*
     * 说明:sysResult对象是系统的返回值对象,调用次数较多
     * 如果每次都要手动添加200/201  则较为繁琐,能否封装一些方法简化代码调用
     * 解决方法: 添加静态方法简化调用
     * */
    public static SysResult fail(){
        return new SysResult(201,"服务器调用失败",null);
    }
    public static SysResult success(){
        return new SysResult(200,"服务器调用成功",null);
    }
    //重载规则:参数不要耦合,否则会有歧义
    public static SysResult success(Object data){
        return new SysResult(200,"服务器调用成功",data);
    }
    public static SysResult success(String msg,Object data){
        return new SysResult(200,msg,data);
    }
}

controller层方法

/**
     * 需求: 根据u/p查询数据库,返回秘钥token
     * URL: /user/login
     * 类型: post
     * 参数: username/password json
     * 返回值: SysResult对象(token)
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){

        String token = userService.findUserByUP(user);
        if(token == null || "".equals(token)){
            //表示用户名和密码错误
            return SysResult.fail();
        }
        //表示用户名和密码正确,返回秘钥信息
        return SysResult.success(token);
    }

serviceImpl层方法

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;
    @Override
    public String findUserByUP(User user) {
        //将密码加密
        byte[] bytes = user.getPassword().getBytes();
        String md5Pass = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Pass);
        //根据用户名和秘闻查询数据库
        User userDB =  userMapper.findUserByUP(user);
       // 判断userDB是否有值
        if (userDB == null){
            return null;
        }
        //String token= "密钥";//暂为密钥,具体之后进行具体编写
        //密钥特点: 唯一性,迷惑性    UUID:可根据时间加上随机数 进行hash计算生成几乎唯一的数 几乎可保证唯一性 例如:40b449d8-28a0-11ec-8433-d8c4973d35a8
        String token= UUID.randomUUID().toString().replace("-", "");
        return token;
    }
}

在userService中编写findUserByUP方法,在mapper包下的UserMapper编写相应的持久层代码。

2.后台首页展示、展示左侧菜单列表

2.1 接口文档

controller层

@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {
    @Autowired
    private RightsService rightsService;
    /*
    *   左侧菜单获取
        请求路径 /rights/getRightsList
        请求类型 GET
        请求参数 无
        响应数据 SysResult(List)对象
        * */
    @GetMapping("/getRightsList")
    public SysResult getRightsList(){
        List<Rights> list = rightsService.getRightsList();
        return SysResult.success(list);
    }
}

mapper,service层省略

RightsMapper.xml

<select id="getRightsList" resultMap="RightsRM">
        SELECT a.id,a.name,a.parent_id,a.path,a.`level`,a.created,a.updated,b.id c_id,b.parent_id c_parent_id,b.name c_name,b.created c_id,b.`level` c_level,b.updated c_update,b.path c_path
        FROM (SELECT * from rights WHERE parent_id = 0) a LEFT JOIN rights b
            on a.id = b.parent_id
    </select>
    <resultMap id="RightsRM" type="rights" autoMapping="true">
        <id property="id" column="id"></id>
        <collection property="children" ofType="rights">
            <id property="id" column="c_id"></id>
            <result property="name" column="c_name"></result>
            <result property="path" column="c_path"></result>
            <result property="level" column="c_level"></result>
            <result property="parentId" column="c_parent_id"></result>
            <result property="created" column="c_create"></result>
            <result property="updated" column="c_updated"></result>
        </collection>
    </resultMap>

效果:

3.完成用户模块crud

3.1实现点击左侧菜单栏跳转到右边空白页面,实现组件父子关系,将用户列表展现
const routes = [
  {path: '/', redirect: '/login'},
  {path: '/login', component: Login},
  {path: '/elementUI', component: ElementUI},
  {path: '/home', component: Home,children:[
      	/*定义父组件的子组件*/
  	{path: '/user', component: User}
  ]} 
]

子组件将会展现在父组件定义的内

<!-- 定义主页面结构-->
      <el-main>
        <!-- 定义路由展现页面-->
        <router-view></router-view>
      </el-main>

用户列表接口文档

封装vo对象PageResult

@Data
@Accessors(chain = true)
public class PageResult {       //定义分页查询对象
    private String query;       //查询参数
    private Integer pageNum;    //查询页数
    private Integer pageSize;   //每页条数
    private Long total;         //总记录数
    private Object rows;        //查询结果
}
3.2用户状态信息的修改

​ 补充知识: vue.js作用域插槽 获取当前行的全部数据

<template slot-scope="scope">//scope 是一个形参,用以获取当前行的全部数据(包括 隐藏的)
               {{scope.row}}
             </template>

接口文档

   @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(@PathVariable("id") Integer id,@PathVariable("status") Boolean status ){
        int i = userService.updatestatus(id,status);
        if ( i>=0){
            return SysResult.success();
        }
      return SysResult.fail();
    }

注意:status字段在数据库为tinyint类型 在sql输入 false会转化为 0 true会转化为1 。

3.3 用户新增

接口文档

[

3.4 用户信息修改


注意: 更新时,业务的回显

 /* 用户更新,数据回显
    根据ID查询用户信息
    请求路径: /user/{id}
    请求类型: GET
    返回值: SysResult对象
    * */
    @GetMapping("/{id}")
    public SysResult findById(@PathVariable("id") Integer id){
        User user = userService.findById(id);
        return SysResult.success(user);
    }/*根据用户ID更新数据
    请求路径: /user/updateUser
    请求类型: PUT
    请求参数: User对象结构*/
    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){
        userService.updateUser(user);
        return  SysResult.success();
    }
   /* 根据ID删除用户
    请求路径: /user/{id}
    请求类型: delete
    请求参数: id
    返回值: SysResult对象*/
    @DeleteMapping("/{id}")
    public SysResult deleteById(@PathVariable("id")Integer id){
        userService.deleteById(id);
        return SysResult.success();
    }

4 全局异常处理

异常说明:

使用 try/catch 缺点:如果代码中添加大量try-catch,使代码结构混乱,代码复杂度变高,不便维护

@DeleteMapping("/{id}")
    public SysResult deleteById(@PathVariable("id")Integer id){
        try{
        userService.deleteById(id);
        return SysResult.success();}catch (Exception e){
            return SysResult.fail();
        }
    }

解决方法 spring 4后 添加了全局异常处理机制

@RestControllerAdvice
//标识该类是全局异常处理机制   返回值都是json串   使用aop的技术,解决特定问题。
//@ControllerAdvice
public class SystemExe {
    /*
    * 说明: 需要为全局异常定义一个方法
    * 要求: 返回统一的业务数据 SysResult
    * 拦截  指定遇到某种异常实现aop处理
    * 特点:  该异常处理机制,只拦截controller层抛出的异常。
    * 注意在我们的业务方法中不要随便使用 try-catch ,他将异常捕获,不再向上层抛出,可能会导致业务执行不成功,却返回正常信息
    * */
    @ExceptionHandler(RuntimeException.class)
    //@ExceptionHandler({RuntimeException.class, SQLException.class})
    //当初出现了运行时异常时 进行拦截 执行下面方法 拦截异常可以为多种
    public SysResult fail(Exception e){
        e.printStackTrace(); //打印异常
        return SysResult.fail();
    }
}
spring事务控制
  1. 事务特性

    1. 原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。强调事务的不可分割
    2. 隔离性:隔离性指的是多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务所干扰,多个并发事务之间要相互隔离。一个事务执行的过程中,不应该受到其他事务的干扰
    3. 一致性:指的是事务必须从一个一致性状态转换到另一个一致性状态,通俗的说就是一个事务执行前后都必须是一致性状态。 事务的执行的前后数据的完整性保持一致.
    4. 持久性:持久性指的是事务一旦提交,他对数据库中数据的改变是永久性的,接下来的其他操作或故障不应对其执行结果产生任何影响。事务一旦结束,数据就持久到数据库
  2. spring默认事务策略(事务控制 未开)

  public SysResult deleteById(@PathVariable("id")Integer id){

        userService.deleteById(id);
        return SysResult.success();
    	int i = 1/0
        //添加了全局异常处理机制,会返回事务删除错误,但数据依然被删除
    }

解决方法:

@DeleteMapping("/{id}")
 /*
 *  @Transactional()----一般加载有数据库修改的方法上,也可以加载该mapper接口方法上。一般不要加在查询方法上,会影响查询效率。
 *   作用:  
 *       1.默认条件下,志兰姐运行时异常
 *       可以有参数:(一般不加参数)
 *           rollbackFor : 指定异常的类型回滚   rollbackFor = RuntimeException.class
 *           noRollbackFor : 指定异常不回滚   noRollbackFor = RuntimeException.class
 * */
 @Transactional()
 public SysResult deleteById(@PathVariable("id")Integer id){

     userService.deleteById(id);
     return SysResult.success();
 }

5.完成商品分类模块crud(之后使用mybatis-plus)

5.1业务接口说明
  1. 商品分类 显示
  • 编辑ItemCatController
@RestController
@CrossOrigin
@RequestMapping("/itemCat")
public class ItemCatController {

    @Autowired
    private ItemCatService itemCatService;

    /**
     * 需求: 查询3级分类数据信息
     * 类型: get
     * URL: /itemCat/findItemCatList/{level}
     * 参数: level
     * 返回值: SysResult(list)
     */
    @GetMapping("/findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){

        List<ItemCat> list = itemCatService.findItemCatList(level);
        return SysResult.success(list);
    }

}
  • 编辑ItemCatService(两种写法)

    • 普通写法
 @Service
public class ItemCatServiceImpl implements ItemCatService{

   @Autowired
   private ItemCatMapper itemCatMapper;

   /**
    * 弊端: 由于多次循环遍历 查询数据库,导致数据库查询次数太多效率极低.
    * @param level
    * @return
    */
   @Override
   public List<ItemCat> findItemCatList(Integer level) {
       //查询一级商品分类信息
       QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
       queryWrapper.eq("parent_id",0);
       List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
       //查询二级商品分类信息
       for(ItemCat oneItemCat: oneList){
           //1.复用条件构造器 将之前的数据清空
           queryWrapper.clear();
           //查询二级数据 parent_id = 一级ID
           queryWrapper.eq("parent_id",oneItemCat.getId());
           List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
           //遍历二级列表 查询三级数据,封装数据返回
           if(teoList.size>0){
               for(ItemCat itemCat: twoList){
                    queryWrapper.clear();
                   queryWrapper.eq("parent_id",itemCat.getId());
                   List<ItemCat> threeItemCat = itemCatMapper.selectList(queryWrapper);
                   twoItemCat.setChildren(threeItemCat);
               }
           }
           oneItemCat.setChildren(twoList);
       }
       return oneList;
   }
}

封装到map内利用<key,value>,key值唯一用以存放 parent_id 然后根据所需要的层级返回封装的值

@Service
public class ItemCatServiceImpl implements ItemCatService{

  @Autowired
  private ItemCatMapper itemCatMapper;

  /**
   *      思路:获取所有的数据库记录,之后按照父子级关系进行封装
   *      数据结构: Map<k,v>
   *               Map<parentId,List当前父级的子级信息(不嵌套)>
   *      例子:     Map<0,List[{id=1,name="xx",children=null}.....]>
   *
   * 封装数据规则:
   *      1.遍历所有的数据.
   *      2.获取parentId
   *      3.判断parentId是否存在,之后实现数据封装
   */

  public Map<Integer,List<ItemCat>> getMap(){
      Map<Integer,List<ItemCat>> map = new HashMap<>();
      //查询所有的数据库记录
      List<ItemCat> list = itemCatMapper.selectList(null);
      //1.遍历数据
      for(ItemCat itemCat:list){
          //获取parentId
          int parentId = itemCat.getParentId();
          if(map.containsKey(parentId)){  //判断集合中是否有key
              //表示数据存在,将自己追加
              map.get(parentId).add(itemCat);
          }else{
              //key不存在, 定义list集合,将自己作为第一个元素追加
              List<ItemCat> childrenList = new ArrayList<>();
              childrenList.add(itemCat);
              //将数据保存到map集合中
              map.put(parentId,childrenList);
          }
      }
      return map;
  }

  //该方法获取1-2级数据信息
  public List<ItemCat> getTwoList(Map<Integer,List<ItemCat>> map){
      //1.先查询一级菜单数据
      List<ItemCat> oneList = map.get(0);
      //2.遍历每个一级菜单去封装二级数据
      for(ItemCat oneItemCat : oneList){
          //parent_id = 一级ID
          int parentId = oneItemCat.getId();
          //查询二级数据
          List<ItemCat> twoList = map.get(parentId);
          //将数据进行封装
          oneItemCat.setChildren(twoList);
      }
      //返回一级数据
      return oneList;
  }

  /**
   * 实现思路:
   *      1. 获取二级分类列表信息
   *      2. 遍历一级菜单,获取二级数据
   *      3. 根据二级菜单查询三级数据   防止二级数据为null的现象
   *      4. 将三级数据封装到二级中
   * @param map
   * @return
   */
  public List<ItemCat> getThreeList(Map<Integer,List<ItemCat>> map){
      //1.获取1-2数据信息  包含了2级的children
      List<ItemCat> oneList = getTwoList(map);
      //2.编辑一级数据,获取二级数据
      for(ItemCat oneItemCat : oneList){
          List<ItemCat> twoList = oneItemCat.getChildren();
          if(twoList == null || twoList.size()==0){
              //跳过本地循环,进入下一次循环
              continue;
          }
          //3.遍历二级数据,查询三级信息
          for (ItemCat twoItemCat : twoList){
              //查询三级  parentId = 二级ID
              int parentId = twoItemCat.getId();
              List<ItemCat> threeList = map.get(parentId);
              //将三级封装到二级中
              twoItemCat.setChildren(threeList);
          }
      }
      return oneList;
  }

  //实现列表数据功能展示
  @Override
  public List<ItemCat> findItemCatList(Integer level) {
      long startTime = System.currentTimeMillis();
      //获取所有集合数据
      Map<Integer,List<ItemCat>> map = getMap();
      if(level == 1){
          //1.一级商品分类信息
          return map.get(0);
      }
      //获取一级菜单和二级菜单
      if(level == 2){
          return getTwoList(map);
      }

      //获取三级菜单数据 1-2-3
      List<ItemCat> allList = getThreeList(map);
      long endTime = System.currentTimeMillis();
      System.out.println("耗时:"+(endTime-startTime)+"毫秒");
      return allList;
  }

效果:

  1. 修改商品分类状态

    • ​ 接口文档

  • controller

/*
*
修改商品分类状态
请求路径: /itemCat/status/{id}/{status}
请求类型: put
请求参数:id status
* */
@PutMapping("/status/{id}/{status}")
public SysResult status(@PathVariable("id") Integer id, @PathVariable("status") Boolean status) {
    itemCatService.status(id, status);
    return SysResult.success();
}
 ```
  • Impl

@Override
@Transactional
public void status(Integer id, Boolean status) {
    ItemCat itemCat = new ItemCat();
    itemCat.setId(id).setStatus(status).setUpdated(new Date());
    itemCatMapper.updateById(itemCat);
}
 ```
  1. 用户新增

    • 接口文档
    • controller
    @PostMapping("/saveItemCat")
     public SysResult saveItemCat(@RequestBody ItemCat itemCat) {
         itemCatService.saveItemCat(itemCat);
         return SysResult.success();
     }
    
    • impl
      @Override
     //事务管理
     @Transactional
     public void saveItemCat(ItemCat itemCat) {
         Date date = new Date();
         itemCat.setStatus(true);
         itemCat.setCreated(date).setUpdated(date);
         itemCatMapper.insert(itemCat);
     }
    
    
  2. 商品分类名修改(和状态修改操作相同,只不过url不同,传入的对象为空的位置不同

  3. 商品分类删除

    • 接口文档

​ controller

@DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(ItemCat itemCat){
        itemCatService.deleteItemCat(itemCat);
        return SysResult.success();
    }

​ impl

*
* 业务: 如果是父级,则应该删除子集和自己
* 思路:
*       1. 判断是否为3级标签,  直接删除
*       2. 判断是否为2级标签,  先删三级,再删二级
*       3. 判断是否为1级标签,  先查询二级,再删除三级二级,再删一级。
* */
@Override
    @Transactional
    public void deleteItemCat(ItemCat itemCat) {
        if(itemCat.getLevel() == 3){
            int id = itemCat.getId();
            itemCatMapper.deleteById(id);
            return;//return 表示程序终止
        }
        if(itemCat.getLevel() == 2){
            int id = itemCat.getId();
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id", id);
           itemCatMapper.delete(queryWrapper);//删除三级数据
            itemCatMapper.deleteById(id);
            return;
        }
        if(itemCat.getLevel() == 1){
         QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
         queryWrapper.eq("parent_id", itemCat.getId());
         //obj方法获取主键   由于是删除的业务,所以只需获得id即可
         List idlist = itemCatMapper.selectObjs(queryWrapper);
         //判断是否有二级数据
            if(idlist.size()>0) {
                //根据二级id删除三级数据  sql  where parent_id in (1,2,3,4)
                queryWrapper.clear();//清空上次的数据
                queryWrapper.in("parent_id", idlist);
                itemCatMapper.delete(queryWrapper);
                //最后删除二级和一级
                idlist.add(itemCat.getId());
            /*
                queryWrapper.clear();
                queryWrapper.in("id", idlist);
                itemCatMapper.delete(queryWrapper);
            */
                itemCatMapper.deleteBatchIds(idlist);
            }else {
                itemCatMapper.deleteById(itemCat.getId());
            }
        }
    }
  1. 完成数据自动填充

    每次进行插入修改操作都要去相应的修改时间,为了简洁代码,我们可以将其他pojo类中的共有属性提取到一个父pojo类中,通过注解,使其在数据操作时自动更新时间

   public class BasePojo implements Serializable{
	//新增操作时自动填充
	@TableField(fill = FieldFill.INSERT)
	private Date created;	//表示入库时需要赋值
	//新增和修改时自动填充
	@TableField(fill = FieldFill.INSERT_UPDATE)
	private Date updated;	//表示入库/更新时赋值.}

为是起生效要配置config类

  /**
 * @author lxb
 * 2021/10/14 9:33
 * MP 自动属性自动填充
 */
@Component  //将对象交给spring容器管理
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created", date,metaObject);
        this.setFieldValByName("updated", date,metaObject);
        //在插入操作时,自动执行   改变属性名   更新数据
    }

    @Override
    public void updateFill(MetaObject metaObject) {

        this.setFieldValByName("updated", new Date(),metaObject);
    }
}

6.完成商品模块crud

  1. 表设计:

​ 注意: price 是实际价格的100倍,在取数据时进行显示时要缩小100倍,存数据时要扩大100倍(保证精度)
2. 页面分析

* 添加商品按钮 */
      async addItemBtn(){
        //console.log(this.addItemForm)

        //1.完成表单校验
        this.$refs.addItemFormRef.validate( valid => {
          if(!valid) return this.$message.error("请输入商品必填项")
        })

        //2.完成商品参数的封装
        //2.0 将商品价格扩大100倍
        this.addItemForm.price = this.addItemForm.price * 100
        //2.1 将商品图片的数据转化为字符串
        this.addItemForm.images = this.addItemForm.images.join(",")

        //2.5 实现商品数据提交
        let submitAddItem = {
          item : this.addItemForm,
          itemDesc: this.itemDesc
        }

        console.log(submitAddItem)
        let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
        if(result.status !== 200) return this.$message.error("商品添加失败")
        this.$message.success("商品添加成功")

        //2.5添加完成之后,将数据重定向到商品展现页面
        this.$router.push("/item")
      }       
  1. 商品列表分页展现(利用MP的selectPage方法进行分页<分页带条件查询>
//controller类   
@Autowired
    private ItemService itemService;
    /*
    * 业务: 实现商品列表分页展现
    * 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
    * 请求类型: get
    * 请求参数: 使用pageResult对象接收
    * 返回值结果:  Sysresult(pageResult)
    * */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){
        pageResult = itemService.getItemList(pageResult);
        return SysResult.success(pageResult);
    }
/*
    * selectPage  语法
    *   1.page  MP内部指定分页对象
    *   2.querywrapper 条件构造器
    * sql:  。。。 where title= "%"#{title}"%"
    * */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //判断是否有值
        Boolean flag = StringUtils.hasLength(pageResult.getQuery());
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.like(flag,"title", pageResult.getQuery());
        //Ipage  接口   Page 类 向上转型
        IPage<Item> page = new Page<>(pageResult.getPageNum(),pageResult.getPageSize());
        page = itemMapper.selectPage(page, queryWrapper);
        long total = page.getTotal();
        List<Item> rows = page.getRecords();
        return pageResult.setTotal(total).setRows(rows);
    }

注意使用selectPage方法要定义MP配置

@Configuration
//表示此类是一个配置类
public class MybatisPlusConfig {
    /*
    * @Bean 将方法的返回值交给spring容器管理
    * */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));//也可以写MYSQL
        return interceptor;
    }
}

  1. 商品新增业务接口

    JS代码分析
/* 添加商品按钮 */
      async addItemBtn(){
        //console.log(this.addItemForm)

        //1.完成表单校验
        this.$refs.addItemFormRef.validate( valid => {
          if(!valid) return this.$message.error("请输入商品必填项")
        })

        //2.完成商品参数的封装
        //2.0 将商品价格扩大100倍
        this.addItemForm.price = this.addItemForm.price * 100
        //2.1 将商品图片的数据转化为字符串
        this.addItemForm.images = this.addItemForm.images.join(",")

        //2.5 实现商品数据提交
        let submitAddItem = {
          item : this.addItemForm,
          itemDesc: this.itemDesc
        }

        console.log(submitAddItem)
        let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
        if(result.status !== 200) return this.$message.error("商品添加失败")
        this.$message.success("商品添加成功")

        //2.5添加完成之后,将数据重定向到商品展现页面
        this.$router.push("/item")
      }   

商品新增业务接口

  • 请求路径: http://localhost:8091/item/saveItem
  • 请求类型: post
  • 前端传递参数分析
{
		item: {
			images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
			itemCatId: 560
			num: "100"
			price: 718800
			sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
			title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
		},
		itemDesc: {
				itemDesc: "<ul><li>品牌:&nbsp;<a href=https://list.jd.com/list.html".......      "
		}
	}


封装IteemVo类

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO {  //该对象封装商品所有的参数信息
    private Item item;
    private ItemDesc itemDesc;
}

编辑controller

/*
    *
    商品分类新增
    请求路径: /itemCat/saveItemCat
    请求类型: post
    请求参数: 表单数据
    ItemCat同时封装了 item 和item_desc 属性
    * */
    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat) {
        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }

​ 4.1 Impl

//由于数据统一提交 所以要确保,并且item.id = itemDesc.id 数据要对应
@Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        item.setStatus(true);
        //要求item动态入库后,动态返回id
        //MP原则:入库后动态回显数据
       itemMapper.insert(item);
       //实现desc入库
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }
富文本编辑器(所见即所得)

​ 1.在所需位置添加标签

<!-- 定义富文本编辑器-->
            <quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc">
            </quill-editor>

​ 2.配置组件引用

/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'

/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme

​ 3.在vue-ui中导入 vue-quill-editor依赖

  1. 实现商品详情入库5

    • itemDesc说明

      tem表是商品的基本信息,itemDesc表是商品的详情信息. 用户一般查询时都是查询的基本信息.只有点击某个商品时才会查询详情信息.为了提高查询效率 将商品分为 item/itemDesc

    • 逻辑关系

      \1. 一个商品对应一个详情
      \2. 一个详情对应一个商品

    • 数据库表示

      item.id = itemDesc.id

    • 商品详情参数传递

      用户点击添加商品时,会将item/itemDesc的对象进行传递.则在后端动态接收数据则可以获取2个对象数据.

    • 编辑ItemDesc POJO对象

      @Data
      @Accessors(chain = true)
      @TableName("item_desc")
      public class ItemDesc extends BasePojo{
          //由于item.id=itemDesc.id 所以ID不能主键自增
          @TableId
          private Integer id;
          private String itemDesc;
      
      }
      
    • 编辑对应mapper接口

      public interface ItemDescMapper extends BaseMapper<ItemDesc> {}
      
    • 编辑对应实现类

      当用户点击入库时,应该将item/itemDesc一起入库操作.

      @Override
          @Transactional
          public void saveItem(ItemVO itemVO) {
              Item item = itemVO.getItem(); //id=null
              item.setStatus(true);
              //要求item入库之后,动态返回Id!!!!
              //MP原则: 入库之后动态回显数据!!
              itemMapper.insert(item);
              //实现itemDesc对象入库
              ItemDesc itemDesc = itemVO.getItemDesc();
              itemDesc.setId(item.getId());
              itemDescMapper.insert(itemDesc);
          }
      

其他商品模块的增删改同上

7.完成商品图片上传

实现文件上传

  1. 在elementUI组件库中查找上传组件,前端代码

文件上传入门案例

8.完成商品图片回显 (nginx反向代理)

9.完成服务器部署,实现负载均衡/搭建tomcat集群

10.实现linux真实环境部署

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- gamedaodao.net 版权所有 湘ICP备2024080961号-6

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务