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

目录

  • spring boot 前后端分离项目(商城项目)学习笔记
    • 后端配置
        • springboot项目 pom.xml文件
        • maven
        • 配置文件
        • 后端
      • 数据库
    • 项目业务实现流程
      • 1.实现用户登录操作
      • 2.后台首页展示、展示左侧菜单列表
        • 2.1 接口文档
      • 3.完成用户模块crud
        • 3.1实现点击左侧菜单栏跳转到右边空白页面,实现组件父子关系,将用户列表展现
        • 3.2用户状态信息的修改
        • 3.3 用户新增
        • 3.4 用户信息修改
      • 4 全局异常处理
        • spring事务控制
      • 5.完成商品分类模块crud(之后使用mybatis-plus)
        • 5.1业务接口说明
      • 6.完成商品模块crud
      • 商品新增业务接口
        • 富文本编辑器(所见即所得)
      • 7.完成商品图片上传
      • 8.完成商品图片回显 (nginx反向代理)
      • 9.完成服务器部署,实现负载均衡/搭建tomcat集群
      • 10.实现linux真实环境部署

代码托管平台:gitee

后端代码:jtMP

前端代码:jtadmin

后端配置

springboot项目 pom.xml文件

1.pom文件 阿里云与原生

​ 原生地址:https://start.spring.io/

​ 阿里云地址:https://start.aliyun.com

​ 原生:标签 阿里云用 以下代替 其中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包查询网址:mvn

  1. Maven 依赖的传递性

例如 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 : rootpassword: 123456url : jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Drivertype: 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. 常用注解

 /**@Controller1. 将该类交给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方法*/
//testpublic 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: truelog-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{@AutowiredUserMapper userMapper;@Overridepublic 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-d8c4973d35a8String token= UUID.randomUUID().toString().replace("-", "");return token;}
}

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

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

2.1 接口文档

controller层

@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {@Autowiredprivate 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_pathFROM (SELECT * from rights WHERE parent_id = 0) a LEFT JOIN rights bon 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 {@Autowiredprivate 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{@Autowiredprivate ItemCatMapper itemCatMapper;/*** 弊端: 由于多次循环遍历 查询数据库,导致数据库查询次数太多效率极低.* @param level* @return*/@Overridepublic 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 = 一级IDqueryWrapper.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{@Autowiredprivate 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){//获取parentIdint 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 = 一级IDint 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级的childrenList<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 = 二级IDint parentId = twoItemCat.getId();List<ItemCat> threeList = map.get(parentId);//将三级封装到二级中twoItemCat.setChildren(threeList);}}return oneList;}//实现列表数据功能展示@Overridepublic 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-3List<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//事务管理@Transactionalpublic 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@Transactionalpublic 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 {@Overridepublic void insertFill(MetaObject metaObject) {Date date = new Date();this.setFieldValByName("created", date,metaObject);this.setFieldValByName("updated", date,metaObject);//在插入操作时,自动执行   改变属性名   更新数据}@Overridepublic 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类
@Autowiredprivate 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}"%"* */@Overridepublic 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容器管理* */@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));//也可以写MYSQLreturn 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: 560num: "100"price: 718800sellPoint: "【华为官方直供,至高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@Transactionalpublic 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不能主键自增@TableIdprivate Integer id;private String itemDesc;}
      
    • 编辑对应mapper接口

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

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

      @Override@Transactionalpublic void saveItem(ItemVO itemVO) {Item item = itemVO.getItem(); //id=nullitem.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真实环境部署

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

  1. Vue+Spring Boot 前后端分离的商城项目开源啦!

    1 新蜂商城 Vue 移动端版本开源啦! 去年开源新蜂商城项目后,就一直在计划这个项目 Vue 版本的改造,2020 年开始开发并且自己私下一直在测试,之前也有文章介绍过测试过程和存在的问题,修改完成 ...

  2. sm4 前后端 加密_这7个开源的Spring Boot前后端分离项目整理给你

    来源|公众号:江南一点雨 前后端分离已经开始逐渐走进各公司的技术栈,不少公司都已经切换到前后端分离开发技术栈上面了,因此建议技术人学习前后端分离开发以提升自身优势.同时,也整理了 7 个开源的 Spr ...

  3. Spring Boot前后端分离项目Session问题解决

    Spring Boot前后端分离项目Session问题解决 参考文章: (1)Spring Boot前后端分离项目Session问题解决 (2)https://www.cnblogs.com/sooo ...

  4. 《Vue+Spring Boot前后端分离开发实战》专著累计发行上万册

                杰哥的学术专著<Vue+Spring Boot前后端分离开发实战>由清华大学出版社于2021年3月首次出版发行,虽受疫情影响但热度不减,受到业界读者的热捧,截至今日 ...

  5. Spring Boot前后端分离之后端开发

    Spring Boot前后端分离开发之后端开发 前后端分离开发概述 相关术语 前后端分离开发概述 接口规范 RESTful API的理解 RESTful风格的特点 URI规范 路径 请求方式 过滤条件 ...

  6. 前后端分离项目_七个开源的 Spring Boot 前后端分离项目,一定要收藏

    来自公众号:江南一点雨 前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开 ...

  7. crm开源系统 tp框架_八个开源的 Spring Boot 前后端分离项目,一定要收藏!

    点击蓝色字关注我们 前后端分离已经在慢慢走进各公司的技术栈,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,也非常建议大家学习一下前后端分离开发,以免在公司干了两三年,SS ...

  8. springboot jwt token前后端分离_「转」七个开源的 Spring Boot 前后端分离项目,建议收藏加转载...

    其实前后端分离本身并不难,后段提供接口,前端做数据展示,关键是这种思想.很多人做惯了前后端不分的开发,在做前后端分离的时候,很容易带进来一些前后端不分时候的开发思路,结果做出来的产品不伦不类,因此松哥 ...

  9. springboot jwt token前后端分离_7个开源的 Spring Boot 前后端分离项目,一定要收藏!...

    前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开发,以免在公司干了两三年 ...

最新文章

  1. java中import用法
  2. 多个if语句并列_P009 python基础之控制语句01
  3. python的快速入门-Python如何快速入门的基础知识
  4. chrome插件下载
  5. Spring Cloud -Zuul
  6. stm32f4xx 的EXTI使用的一般步骤
  7. H5 的getImageData造成的大量内存开销
  8. SQL 查询语句总是先执行 SELECT?
  9. 20应用统计考研复试要点(part21)--概率论与数理统计
  10. 深夜,在这个已不再喧嚣的城市中寻找到一片属于自己的宁静,仰望那片属于自己的星空……...
  11. Unity 基础知识参考
  12. MIKE水动力笔记10_潮汐调和分析与绘制同潮时线图
  13. java商城系统设计-----积分商城系统
  14. matlab三轴机械臂,MATLAB 中的机械臂算法——路径规划
  15. 好听无损的flac格式歌曲怎么转换成mp3格式的
  16. 表情识别 [传统方法 VS 深度学习方法]
  17. 用python输出圣诞树_教你怎样用Python画了一棵圣诞树,赶紧来学习
  18. 苹果暗黑模式_微信暗黑模式终于来了!这次微信对苹果认怂了?腾讯张军回应......
  19. Windows 修改MAC地址
  20. 找不到msvcr110.dll

热门文章

  1. ANTLR4(四) 基础设计语法
  2. CentOS7 Systemtap 安装
  3. docker国内镜像站点
  4. 分布式架构php,php分布式架构
  5. 不要和一种编程语言厮守终生:为工作正确选择(转)
  6. Android APP安全测试Checklist
  7. 火车WiFi难点在哪里?
  8. 《道德经》第三十八章
  9. java接口返回xml格式_Java xml数据格式返回实现操作
  10. 微信BUG之微信内置的浏览器中window.location.href 不跳转