spring boot 前后端分离项目(商城项目)学习笔记
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
- Maven 依赖的传递性
例如 A项目依赖 b.jar,b.jar依赖c.jar 所以依赖b,jar相当于依赖了b,jar,c.jar.
- Maven打包方式:
- 1.默认是jar
- 2.web项目可以打war
- 3.如果该项目是父级,pom
Maven 依赖的传递性实现原理:
步骤:
当maven开始解析pom.xml文件,根据依赖坐标,找到指定的jar文件,添加该依赖
然后根据相应jar包的pom文件,进行扫描
扫描文件中的dependent
根据dependent坐标重复上述操作
文件传输有效性(以上图)
保证通过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: “下雨”
配置环境切换
可以在原有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
后端
注意 我们缩写的dao包,controller包。。。。应该和主启动类处于同一包下,否则无法内识别
常用注解
/**@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 试用其要先在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 用户对应角色表
项目业务实现流程
创建spring boot项目
编辑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>
配置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 ```
按照数据库建立对应pojo类
导入前端vue框架项目
在命令指令框 vue ui 启动框架
1.实现用户登录操作
1.1 用户登录验证接口
1.2 业务逻辑
- 用户输入用户名和密码 admin123/admin123456 点击登录按钮
- 通过vue中的 axios 发起post请求 /user/login,实现用户登录
- 在UserController中接收用户的请求和参数(json).
- 先将密码进行加密处理(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事务控制
事务特性
- 原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。强调事务的不可分割
- 隔离性:隔离性指的是多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务所干扰,多个并发事务之间要相互隔离。一个事务执行的过程中,不应该受到其他事务的干扰
- 一致性:指的是事务必须从一个一致性状态转换到另一个一致性状态,通俗的说就是一个事务执行前后都必须是一致性状态。 事务的执行的前后数据的完整性保持一致.
- 持久性:持久性指的是事务一旦提交,他对数据库中数据的改变是永久性的,接下来的其他操作或故障不应对其执行结果产生任何影响。事务一旦结束,数据就持久到数据库
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业务接口说明
- 商品分类 显示
- 编辑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;}
效果:
修改商品分类状态
- 接口文档
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);
}```
用户新增
- 接口文档
- 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);}
- 接口文档
商品分类名修改(和状态修改操作相同,只不过url不同,传入的对象为空的位置不同
商品分类删除
- 接口文档
- 接口文档
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());}}}
完成数据自动填充
每次进行插入修改操作都要去相应的修改时间,为了简洁代码,我们可以将其他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
- 表设计:
注意: 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")}
- 商品列表分页展现(利用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;}
}
- 商品新增业务接口
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>品牌: <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依赖
实现商品详情入库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.完成商品图片上传
实现文件上传
- 在elementUI组件库中查找上传组件,前端代码
文件上传入门案例
8.完成商品图片回显 (nginx反向代理)
9.完成服务器部署,实现负载均衡/搭建tomcat集群
10.实现linux真实环境部署
spring boot 前后端分离项目(商城项目)学习笔记相关推荐
- Vue+Spring Boot 前后端分离的商城项目开源啦!
1 新蜂商城 Vue 移动端版本开源啦! 去年开源新蜂商城项目后,就一直在计划这个项目 Vue 版本的改造,2020 年开始开发并且自己私下一直在测试,之前也有文章介绍过测试过程和存在的问题,修改完成 ...
- sm4 前后端 加密_这7个开源的Spring Boot前后端分离项目整理给你
来源|公众号:江南一点雨 前后端分离已经开始逐渐走进各公司的技术栈,不少公司都已经切换到前后端分离开发技术栈上面了,因此建议技术人学习前后端分离开发以提升自身优势.同时,也整理了 7 个开源的 Spr ...
- Spring Boot前后端分离项目Session问题解决
Spring Boot前后端分离项目Session问题解决 参考文章: (1)Spring Boot前后端分离项目Session问题解决 (2)https://www.cnblogs.com/sooo ...
- 《Vue+Spring Boot前后端分离开发实战》专著累计发行上万册
杰哥的学术专著<Vue+Spring Boot前后端分离开发实战>由清华大学出版社于2021年3月首次出版发行,虽受疫情影响但热度不减,受到业界读者的热捧,截至今日 ...
- Spring Boot前后端分离之后端开发
Spring Boot前后端分离开发之后端开发 前后端分离开发概述 相关术语 前后端分离开发概述 接口规范 RESTful API的理解 RESTful风格的特点 URI规范 路径 请求方式 过滤条件 ...
- 前后端分离项目_七个开源的 Spring Boot 前后端分离项目,一定要收藏
来自公众号:江南一点雨 前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开 ...
- crm开源系统 tp框架_八个开源的 Spring Boot 前后端分离项目,一定要收藏!
点击蓝色字关注我们 前后端分离已经在慢慢走进各公司的技术栈,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,也非常建议大家学习一下前后端分离开发,以免在公司干了两三年,SS ...
- springboot jwt token前后端分离_「转」七个开源的 Spring Boot 前后端分离项目,建议收藏加转载...
其实前后端分离本身并不难,后段提供接口,前端做数据展示,关键是这种思想.很多人做惯了前后端不分的开发,在做前后端分离的时候,很容易带进来一些前后端不分时候的开发思路,结果做出来的产品不伦不类,因此松哥 ...
- springboot jwt token前后端分离_7个开源的 Spring Boot 前后端分离项目,一定要收藏!...
前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开发,以免在公司干了两三年 ...
最新文章
- java中import用法
- 多个if语句并列_P009 python基础之控制语句01
- python的快速入门-Python如何快速入门的基础知识
- chrome插件下载
- Spring Cloud -Zuul
- stm32f4xx 的EXTI使用的一般步骤
- H5 的getImageData造成的大量内存开销
- SQL 查询语句总是先执行 SELECT?
- 20应用统计考研复试要点(part21)--概率论与数理统计
- 深夜,在这个已不再喧嚣的城市中寻找到一片属于自己的宁静,仰望那片属于自己的星空……...
- Unity 基础知识参考
- MIKE水动力笔记10_潮汐调和分析与绘制同潮时线图
- java商城系统设计-----积分商城系统
- matlab三轴机械臂,MATLAB 中的机械臂算法——路径规划
- 好听无损的flac格式歌曲怎么转换成mp3格式的
- 表情识别 [传统方法 VS 深度学习方法]
- 用python输出圣诞树_教你怎样用Python画了一棵圣诞树,赶紧来学习
- 苹果暗黑模式_微信暗黑模式终于来了!这次微信对苹果认怂了?腾讯张军回应......
- Windows 修改MAC地址
- 找不到msvcr110.dll