Spring Boot电商项目入门与实战
- 重量级的电商项目,前后端彻底分离
- 体验企业级开发流程:需求分析、设计、编码、测试、上线
- 用户管理、商品分析、商品管理、购物车、订单等核心功能
- 数据库的设计与实现
- 应用RESTful进行接口设计,并使用Postman进行接口测试
- 应用MyBatis generator代码自动生成插件,提升开发效率
- 使用阿里云完成项目的线上部署
- 使用Swagger2构建强大的API文档
- 使用Redis对商品分类信息进行缓存
- 使用JSR-303实现请求参数校验
Spring Boot入门
软件版本
- Java 1.8.0
- MySQL 8.0.12
- Maven 3.3.9
- Spring Boot 2.2.1(严格一致)
Spring Boot诞生历史
- Spring的缺点:配置过于繁琐
- Spring Boot开发团队:Pivotal
Spring Boot简介
- 简化初始搭建以及开发过程
- 不再需要定义样板化的配置
- 快速应用开发领域
Spring、Spring MVC、Spring Boot
- Spring最初利用IOC和AOP解耦
- 按照这种模式搞了MVC框架
- 写很多样板代码很麻烦,就有了Spring Boot
- Spring Cloud是在Spring Boot基础上
Spring Boot核心特点
Spring Boot版本介绍
- CURRENT 2.2.1 CURRENT GA [最新版本]
- GA 2.2.2 SNAPSHOT [GA=General Availability 面向大众稳定版本 版本永恒不变]
- SNAPSHOT 2.1.11 SNAPSHOT [快照 版本随时被修改]
- 如何选择版本? 2.1.10 GA
新建Spring Boot项目演示
第一个接口开发
com/imooc/springbootlearn/SpringBootlearnApplication.java
package com.imooc.springbootlearn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootlearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootlearnApplication.class, args);
}
}
com/imooc/springbootlearn/ParaController.java
package com.imooc.springbootlearn;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 演示各种传参形式
*/
@RestController
public class ParaController {
@GetMapping({"/firstrequest"})
public String firstRequest(){
return "第一个Spring Boot接口";
}
}
pom.xml
<?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.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>SpringBootlearn</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBootlearn</name>
<description>Demo project for Spring Boot</description>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
多种配置URL的方式
com/imooc/springbootlearn/ParaController.java
package com.imooc.springbootlearn;
import org.springframework.web.bind.annotation.*;
/**
* 演示各种传参形式
*/
@RestController
@RequestMapping("/prefix")//统一增加公共前缀
public class ParaController {
@GetMapping({"/firstrequest"})
public String firstRequest() {
return "第一个Spring Boot接口";
}
//@RequestParam 从请求中找到这个参数进行绑定
@GetMapping({"/requestpara"})
public String requestpara(@RequestParam Integer num) {
return "para from request: " + num;
}
//@PathVariable 从URL中寻找对应的参数进行绑定
@GetMapping({"/para/{num}"})
public String pathpara(@PathVariable Integer num) {
return "para from path: " + num;
}
//多URL的用法
@GetMapping({"/multiurl1", "/multiurl2"})
public String multiurl(@RequestParam Integer num) {
return "para from path: " + num;
}
//增加程序健壮性 不一定必须传值
@GetMapping({"/required"})
public String required(@RequestParam(required =
false, defaultValue = "0") Integer num) {
return "para form request: " + num;
}
}
Web项目的三层结构
- Controller职责:对外暴露接口
- Service职责:复杂业务场景下对业务逻辑做一层抽象和封装,保持Controller的简洁和独立,抽象出来的Service可以被Controller重复调用。具体业务代码写在service层,Controller只做简单的逻辑判断
- DAO层职责:和数据相关的、增删改查数据库代码
配置文件简介
可以自动转换的网址:在线yaml转properties-在线properties转yaml-ToYaml.com
Properties:
server.port=8081
server.servlet.context-path=/first
============================================================
YAML:
server:
port: 8081
servlet:
context-path: /first
进行自定义配置
注解类配置载入数据[利用@Value注解]
com/imooc/springbootlearn/PropertiesController.java
package com.imooc.springbootlearn;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 演示读取配置的Controller
*/
@RestController
public class PropertiesController {
//用@Value将properties的设置值进行绑定
@Value("${school.grade}")
Integer grade;
@Value("${school.classnum}")
Integer classnum;
@GetMapping("/gradeclass")
public String gradeClass(){
return "年级: " + grade + " 班级:" + classnum;
}
}
resoources/application.properties
#server.port=8081
##对于整个项目建立统一的前缀 http://127.0.0.1:8081/first/prefix/required
#server.servlet.context-path=/first
#school.方便分类
school.grade=3
school.classnum=6
配置类文件配置载入数据
com/imooc/springbootlearn/SchoolConfig.java
package com.imooc.springbootlearn;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* School配置类
*/
@Component
@ConfigurationProperties(prefix = "school")
public class SchoolConfig {
//自动去配置信息里寻找且绑定
Integer grade;
Integer classnum;
public Integer getGrade() {
return grade;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
public Integer getClassnum() {
return classnum;
}
public void setClassnum(Integer classnum) {
this.classnum = classnum;
}
}
com/imooc/springbootlearn/ConfigController.java
package com.imooc.springbootlearn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 读取配置类
*/
@RestController
public class ConfigController {
@Autowired
SchoolConfig schoolConfig;
@GetMapping({"/gradefromconfig"})
public String gradeclass(){
return "年级: " + schoolConfig.grade + " 班级:" + schoolConfig.classnum;
}
}
Service和Dao的编写
com/imooc/springbootlearn/Student.java
package com.imooc.springbootlearn;
/**
* 学生实体类
*/
public class Student {
Integer id;
String name;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
com/imooc/springbootlearn/StudentController.java
package com.imooc.springbootlearn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 学生Controller
*/
@RestController
public class StudentController {
@Autowired
StudentService studentService;
//服务层 去中转
@GetMapping("/student")
public String student(@RequestParam Integer num){
Student student = studentService.findStudent(num);
return student.toString();
}
}
/*
联系
@Autowired和@Resource注解都是作为bean对象注入的时候使用的
两者都可以声明在字段和setter方法上
注意:如果声明在字段上,那么就不需要再写setter方法。但是本质上,该对象还是作为set方法的实参,通过执行set方法注入,只是省略了setter方法罢了
区别
@Autowired注解是Spring提供的,而@Resource注解是J2EE本身提供的
@Autowird注解默认通过byType方式注入,而@Resource注解默认通过byName方式注入
@Autowired注解注入的对象需要在IOC容器中存在,否则需要加上属性required=false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
*/
com/imooc/springbootlearn/StudentService.java
package com.imooc.springbootlearn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 学生Service 之后调用mapper操作数据库[interface]
*/
@Service
public class StudentService {
@Autowired
StudentMapper studentMapper;
public Student findStudent(Integer id){
return studentMapper.findById(id);
}
}
com/imooc/springbootlearn/StudentMapper.java 【接口】
package com.imooc.springbootlearn;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
/**
* 学生Mapper
*/
@Mapper
@Repository
public interface StudentMapper {
@Select("select * from students where id = #{id}")
Student findById(Integer id);
}
<?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.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>springbootlearn</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBootlearn</name>
<description>Demo project for Spring Boot</description>
<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.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
com/imooc/springbootlearn/SpringBootlearnApplication.java
package com.imooc.springbootlearn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages="com.imooc")
public class SpringBootlearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootlearnApplication.class, args);
}
}
application.properties
#server.port=8081
#server.servlet.context-path=/first
school.grade=10
school.classnum=6
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
SpringBoot电商项目
慕慕生鲜前台
慕慕生鲜后台
课程整体介绍
- 为什么要做电商项目
- 项目亮点以及功能模块介绍
- 项目演示
- 项目开发所需要工具准备
- 数据库设计与项目初始化
- 功能模块开发
- 阿里云部署
- 项目总结
项目亮点
- 最新的业界的互联网企业的优质技术
- 代码规范简介,充分优化
- 流程完整、电商功能丰富
- 前后端彻底分离,符合未来趋势,学了就能用
功能模块介绍
项目功能:
前台 {用户、商品分类、商品信息、购物车、订单}
- 用户模块{注册、登录、更新签名、身份认证、登出}
- 商品分类模块{多级目录、递归查询、缓存}
- 商品模块{商品搜索、商品排序、商品列表、目录展示、商品详情}
- 购物车模块{加入商品、列表显示、数量更改、删除商品、勾选反选、全选全不选}
- 订单模块{下单、订单流程、订单详情、取消订单、支付二维码、扫码支付、个人订单、确认收货}
后台 {用户、商品分类、商品信息、订单}
- 管理员模块{登录登出、身份认证、安全限制}
- 商品分类模块{分类列表、增加分类、修改分类、删除分类}
- 商品模块{商品列表、新增商品、图片上传、更新删除、批量上下架}
- 订单模块{订单列表、地址信息、发货、订单完结}
项目演示
- 前后端分离
- 接口文档
- 全栈、CTO
- 核心是接口的设计
项目开发所需工具准备
- IDEA常用优质插件介绍
- Maven Helper
- Free MyBatis plugin [跳转/识别mapper语法错误]
- Postman安装和常用功能演示
- MySQL可视化工具
数据库设计与项目初始化
- 表设计
- 技术选型、思路
- 新建项目,整合Mybatis,跑通接口
- 引用log4j2日志组件
- 使用AOP统一处理Web请求日志 [请求的参数 返回的商品、字段]
技术选型
- Spring Boot 2.2.1RELEASE
- MyBatis 3.4.6(优点)
- Maven 3.6.1
技术选型需要考虑的点
- 选择你最熟悉的技术 [最好不要超过30%的新技术]
- 选择拥有强大社区支撑的开源技术
- 确保技术前进步伐
- 学会从业务端开始思考
- 重视经验
项目初始化
- 新建项目
- mybatis-generator的安装配置
- 自动生成DAO层文件
- 跑通接口
【下方链接的主要问题就是 由于项目中的jdk和你实际安装的jdk不匹配】
{修改Project Structure → Project的版本}
{修改启动Edit的版本}
{修改setting→Build,Execution,Deployment→Compiler→Java Compiler中的Project version以及项目的Target version}
【Java异常】完美解决this version of the Java Runtime only recognizes class file versions up to xx.0异常_java runtime (class file version 61.0), this versi-CSDN博客
fileversion 55.0 this version of the Java Runtime only recognizes class file versions up to 52.0:已解决_class file version 55.0-CSDN博客
彻底解决:IDEA java: 警告: 源发行版 17 需要目标发行版 17-CSDN博客
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 配置文件,放在resource目录下即可 -->
<!--数据库驱动个人配置-->
<classPathEntry
location="/Users/Pluminary/.m2/repository/mysql/mysql-connector-java/8.0.18/mysql-connector-java-8.0.18.jar"/>
<context id="MysqlTables" targetRuntime="MyBatis3">
<property name="autoDelimitKeywords" value="true"/>
<!--可以使用``包括字段名,避免字段名与sql保留字冲突报错-->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- optional,旨在创建class时,对注释进行控制 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"
userId="root"
password="root">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.imooc.mall.model.pojo"
targetProject="src/main/java">
<!-- 是否允许子包,即targetPackage.schemaName.tableName -->
<property name="enableSubPackages" value="true"/>
<!-- 是否对类CHAR类型的列的数据进行trim操作 -->
<property name="trimStrings" value="true"/>
<!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 -->
<property name="immutable" value="false"/>
</javaModelGenerator>
<!--生成mapper映射文件存放位置-->
<sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类存放位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.imooc.mall.model.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成对应表及类名-->
<table schema="root" tableName="imooc_mall_cart" domainObjectName="Cart"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="imooc_mall_category" domainObjectName="Category" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="imooc_mall_order" domainObjectName="Order" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="imooc_mall_order_item" domainObjectName="OrderItem"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="imooc_mall_product" domainObjectName="Product" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="imooc_mall_user" domainObjectName="User" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
application.properties
spring.datasource.name=imooc_mall_datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
pom.xml
<?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.imooc</groupId>
<artifactId>mall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>small</name>
<description>small</description>
<properties>
<java.version>17</java.version>
</properties>
<!-- 1.依赖mysql-connector-java mybatis-spring-boot-starter 加入generatorConfig.xml 写下面的Plugins-->
<!-- 2. 加入imooc_mall_local.sql后 点Maven -> Plugins -> mybatis-generator -> 点击第一个 -->
<!-- 通过插件生成 此时就出来了com/imooc/mall/model/dao里面所有的 和 com/imooc/mall/model/pojo里面所有的 和 mappers-->
<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>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
</project>
package com.imooc.mall.controller;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 用户控制器
*/
@Controller
public class UserController {
//4.返回对象的基本信息 return里面会写和service相关的 5建立Service层
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody //返回Json格式内容
public User personalPage(){
// 6.补全return 7告诉mapper在哪里怎么去找 去application.properties编写 mybatis.mapper-locations:......
// 去主类里编写@MapperScan(basePackages = "com.imooc.mall.model.dao") 以防找不到mapper 8.去配置端口8083
// 9.加入log4j2.xml
return userService.getUser();
}
}
com/imooc/mall/service/UserService.java
package com.imooc.mall.service;
import com.imooc.mall.model.pojo.User;
//5.这是抽象的接口 还要让它实现 再创建一个impl 实现接口类 UserServiceImpl.java
public interface UserService {
User getUser();
}
com/imooc/mall/service/impl/UserServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.model.dao.UserMapper;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* UserService实现类
*/
@Service
//5.重写里面的方法 @Autowired引入一个mapper去查询数据库返回真正的信息 6返回UserController补全return
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser(){
//通过主键来查询一个对象
return userMapper.selectByPrimaryKey(1);
}
}
package com.imooc.mall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.mall.model.dao")
public class MallApplication {
public static void main(String[] args) {
SpringApplication.run(MallApplication.class, args);
}
}
log4j2日志
- 日志级别(优先级降低):error, warn, info, debug, trace
- 排除Logback依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
<Properties>
<Property name="baseDir" value="${sys:user.home}/Desktop/idea_Space/logs"/>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT"
onMismatch="DENY"/>
<PatternLayout
pattern="[%d{MM:dd HH:mm:ss.SSS}] [%level] [%logger{36}] - %msg%n"/>
</Console>
<!--debug级别日志文件输出-->
<RollingFile name="debug_appender" fileName="${baseDir}/debug.log"
filePattern="${baseDir}/debug_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在debug及以上在info以下 -->
<ThresholdFilter level="debug"/>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<!-- 策略 -->
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<!-- info级别日志文件输出 -->
<RollingFile name="info_appender" fileName="${baseDir}/info.log"
filePattern="${baseDir}/info_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在info及以上在error以下 -->
<ThresholdFilter level="info"/>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<!-- 策略 -->
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<!-- error级别日志文件输出 -->
<RollingFile name="error_appender" fileName="${baseDir}/error.log"
filePattern="${baseDir}/error_%i.log.%d{yyyy-MM-dd}">
<!-- 过滤器 -->
<Filters>
<!-- 限制日志级别在error及以上 -->
<ThresholdFilter level="error"/>
</Filters>
<!-- 日志格式 -->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<!-- 每隔一天转存 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<!-- 文件大小 -->
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="debug_appender"/>
<AppenderRef ref="info_appender"/>
<AppenderRef ref="error_appender"/>
</Root>
</Loggers>
</Configuration>
AOP统一处理Web请求日志
- 为什么需要AOP统一处理Web请求日志 [对系统健壮性的保证 创建filter]
com/imooc/mall/filter/WebLogAspect.java
package com.imooc.mall.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 打印请求和响应信息
*/
@Aspect
@Component
public class WebLogAspect {
//生成loger类
private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
//10.增加一个拦截点AOP
@Pointcut("execution(public * com.imooc.mall.controller..*.*(..))")
public void webLog(){
}
// 10.提供请求参数
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
//收到请求,记录请求内容 请求到来所作的事情
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("URL: " + request.getRequestURI().toString());
log.info("HTTP_METHOD: " + request.getMethod());
log.info("IP: " + request.getRemoteAddr());
log.info("CLASS_METHOD: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("ARGS: " + Arrays.toString(joinPoint.getArgs()));
}
// 11.返回的时候也要拦截 返回参数res 拦截点webLog
@AfterReturning(returning = "res",pointcut = "webLog()")
public void doAfterReturning(Object res)throws Exception{
//处理完请求,返回内容
log.info("RESPONSE: " + new ObjectMapper().writeValueAsString(res));
}
}
用户模块
- 整体介绍:知识点、功能点
[登录、注册、重名校验、密码加密存储、Session的使用、越权校验、统一响应对象、异常枚举、Java异常体系、Postman实操、统一异常处理、更新个人信息]
- 接口设计
- 编码设计
API统一返回对象
package com.imooc.mall.common;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
/**
* 通用返回对象 T有可能是返回的购物车对象
* 12.编写响应API 13创建枚举异常com/imooc/mall/exception/ImoocMallExceptionEnum.java
*/
public class ApiRestResponse<T> {
private Integer status;
private String msg;
private T data;
private static final int OK_CODE = 10000;
private static final String OK_MSG = "SUCCESS";
public ApiRestResponse(Integer status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public ApiRestResponse(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public ApiRestResponse() {
//默认请求信息
this(OK_CODE, OK_MSG);
}
public static<T> ApiRestResponse<T> success(){
// 建立带着10000 和 SUCCESS的方法
return new ApiRestResponse<>();
}
public static <T> ApiRestResponse<T> error(Integer code, String msg) {
return new ApiRestResponse<>(code, msg);
}
// 14.用枚举来搞错误 为了方便调试编写完后生成一个toString方法 15修改UserController的register()
public static <T> ApiRestResponse<T> error(ImoocMallExceptionEnum ex) {
return new ApiRestResponse<>(ex.getCode(), ex.getMsg());
}
// 把错误[异常]创建成一个枚举类
public static<T> ApiRestResponse<T> success(T result){
// 两个成功success的方法
ApiRestResponse<T> response = new ApiRestResponse<>();
response.setData(result);
return response;
}
@Override
public String toString() {
return "ApiRestResponse{" +
"status=" + status +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
package com.imooc.mall.exception;
/**
* 异常枚举
*/
//13.编写异常枚举 注意类是enum噢 14返回ApiRestResponse
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位");
//异常码
Integer code;
//异常信息
String msg;
ImoocMallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
注册接口开发
com/imooc/mall/controller/UserController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 用户控制器
*/
@Controller
public class UserController {
//4.返回对象的基本信息 return里面会写和service相关的 5建立Service层
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody //返回Json格式内容
public User personalPage(){
// 6.补全return 7告诉mapper在哪里怎么去找 去application.properties编写 mybatis.mapper-locations:......
// 去主类里编写@MapperScan(basePackages = "com.imooc.mall.model.dao") 以防找不到mapper 8.去配置端口8083
// 9.加入log4j2.xml 增加aop的pom
return userService.getUser();
}
// 12.创造一个统一返回对象 com/imooc/mall/common/ApiRestResponse.java
// 15.参数加在请求中所以加上@RequestParam 编写校验 16去UserService增加register接口
@PostMapping("/register")
@ResponseBody
public ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws ImoocMallException {
// 字符串为空 || 符合参数
if (StringUtils.isEmpty(userName)){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_USER_NAME);
}if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_PASSWORD_NAME);
}
// 防止用户密码长度设置简单 密码长度不能少于8
if (password.length()<8){
return ApiRestResponse.error(ImoocMallExceptionEnum.PASSWORD_TOO_SHORT);
}
//18.补全操作 19进行统一处理异常[对前端安全考虑] GlobalExceptionHandler.java
userService.register(userName, password);
return ApiRestResponse.success();
}
}
=========================================
http://127.0.0.1:8083/register
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Mar 06 00:06:06 CST 2024
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'GET' not supported
因为注册的时候采用的是@PostMapping("/register")
单纯查询用get 往数据库写东西post
@RequestMapping("/register")是GET和POST都支持的 但是不推荐
根据不同的业务逻辑选择类型
打开postman 新建一个去查询post
POST: 127.0.0.1:8083/register?userName=mumu&password=12345678
{
"status": 10000,
"msg": "SUCCESS",
"data": null
}
com/imooc/mall/service/UserService.java
package com.imooc.mall.service;
import com.imooc.mall.model.pojo.User;
//5.这是抽象的接口 还要让它实现 再创建一个impl 实现接口类 UserServiceImpl.java
public interface UserService {
User getUser();
// 16.写完接口去实现接口UserServiceImpl.java
void register(String userName, String password);
}
com/imooc/mall/service/impl/UserServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.dao.UserMapper;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* UserService实现类
*/
@Service
//5.重写里面的方法 @Autowired引入一个mapper去查询数据库返回真正的信息 6返回UserController补全return
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser(){
//通过主键来查询一个对象
return userMapper.selectByPrimaryKey(1);
}
@Override
public void register(String userName, String password) throws ImoocMallException {
// 16.查询用户名是否存在, 不允许重名 用userMapper去查数据
// 但未编写功能 去手动编写UserMapper.java
User result = userMapper.selectByName(userName);
if (result != null){
// 17. 用户已存在 在Service层不能直接return但是controller可以直接返回
// 创建一个异常类 com/imooc/mall/exception/ImoocMallException.java
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
// 通过检测 允许写入数据库
User user = new User();
user.setUsername(userName);
user.setPassword(password);
//先判断是不是空 不是空才修改 实现完以后回到controller层进行调用
int count = userMapper.insertSelective(user);
if (count==0){
throw new ImoocMallException(ImoocMallExceptionEnum.INSERT_FAILED);
}
}
}
com/imooc/mall/model/dao/UserMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
int deleteByPrimaryKey(Integer id);
int insert(User record);
int insertSelective(User record);
User selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
// 16. 新增功能 去对应的UserMapper.xml进行描述 117行 BaseResultMap就是一开始自动生成的User对象
// <include refid="Base_Column_List"/> 选取完整的User对象
User selectByName(String userName);
}
mappers/UserMapper.xml
<?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.imooc.mall.model.dao.UserMapper">
<resultMap id="BaseResultMap" type="com.imooc.mall.model.pojo.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="personalized_signature" jdbcType="VARCHAR" property="personalizedSignature" />
<result column="role" jdbcType="INTEGER" property="role" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>
<sql id="Base_Column_List">
id, username, `password`, personalized_signature, `role`, create_time, update_time
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from imooc_mall_user
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from imooc_mall_user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.imooc.mall.model.pojo.User">
insert into imooc_mall_user (id, username, `password`,
personalized_signature, `role`, create_time,
update_time)
values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
#{personalizedSignature,jdbcType=VARCHAR}, #{role,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP},
#{updateTime,jdbcType=TIMESTAMP})
</insert>
<insert id="insertSelective" parameterType="com.imooc.mall.model.pojo.User">
insert into imooc_mall_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="username != null">
username,
</if>
<if test="password != null">
`password`,
</if>
<if test="personalizedSignature != null">
personalized_signature,
</if>
<if test="role != null">
`role`,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="username != null">
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
<if test="personalizedSignature != null">
#{personalizedSignature,jdbcType=VARCHAR},
</if>
<if test="role != null">
#{role,jdbcType=INTEGER},
</if>
<if test="createTime != null">
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.imooc.mall.model.pojo.User">
update imooc_mall_user
<set>
<if test="username != null">
username = #{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
`password` = #{password,jdbcType=VARCHAR},
</if>
<if test="personalizedSignature != null">
personalized_signature = #{personalizedSignature,jdbcType=VARCHAR},
</if>
<if test="role != null">
`role` = #{role,jdbcType=INTEGER},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=TIMESTAMP},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.imooc.mall.model.pojo.User">
update imooc_mall_user
set username = #{username,jdbcType=VARCHAR},
`password` = #{password,jdbcType=VARCHAR},
personalized_signature = #{personalizedSignature,jdbcType=VARCHAR},
`role` = #{role,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=TIMESTAMP},
update_time = #{updateTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from imooc_mall_user
where username = #{userName,jdbcType=VARCHAR}
</select>
</mapper>
com/imooc/mall/exception/ImoocMallException.java
package com.imooc.mall.exception;
/**
* 17. 统一异常(感觉像递归之前编写的枚举异常)
*/
public class ImoocMallException extends Exception{
private final Integer code;
private final String message;
public ImoocMallException(Integer code, String message) {
this.code = code;
this.message = message;
}
public ImoocMallException(ImoocMallExceptionEnum exceptionEnum) {
this(exceptionEnum.getCode(), exceptionEnum.getMsg());
}
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
package com.imooc.mall.exception;
/**
* 异常枚举
*/
//13.编写异常枚举 注意类是enum噢 14返回ApiRestResponse
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试");
//异常码
Integer code;
//异常信息
String msg;
ImoocMallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
GlobalExceptionHandler编写
- 对前端安全考虑,敏感信息不会暴露给用户
- 抛出异常,直接转化为Json的APIResponse
- 抛出重名异常
- 拦截异常并且转变成APIRespond统一类型输出
com/imooc/mall/exception/GlobalExceptionHandler.java
package com.imooc.mall.exception;
import com.imooc.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 19.处理统一异常的handler 业务异常 处理不同逻辑异常
20对密码进行MD5加密UserServiceImpl 先创建MD5Utils
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 统一处理Exception.class异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception e) {
log.error("Default Exception: ", e);
return ApiRestResponse.error(ImoocMallExceptionEnum.SYSTEM_ERROR);
}
@ExceptionHandler(ImoocMallException.class)
@ResponseBody
public Object handleImoocMallException(ImoocMallException e) {
log.error("ImoocMallException: ", e); //传进来的是什么就传出去
return ApiRestResponse.error(e.getCode(), e.getMessage());
}
}
对密码进行MD5保护
com/imooc/mall/util/MD5Utils.java
package com.imooc.mall.util;
import com.imooc.mall.common.Constant;
import org.apache.tomcat.util.codec.binary.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//20.不具备解密的协议 哈希算法 因为有破解MD5的网站所以需要加盐 21com/imooc/mall/common/Constant.java
public class MD5Utils {
public static String getMD5Str(String strValue) throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
return Base64.encodeBase64String(md5.digest((strValue+ Constant.SALT).getBytes()));
}
// 用这个方法测试生成MD5的值
public static void main(String[] args) throws NoSuchAlgorithmException {
String md5Str = getMD5Str("12345");
System.out.println(md5Str);
}
}
com/imooc/mall/common/Constant.java
package com.imooc.mall.common;
/**
* 21.SALT常量值 写完以后去UserServiceImpl重写密码代码
*/
public class Constant {
public static final String SALT = "aSp[PCx,aw.xq246}";
}
com/imooc/mall/service/impl/UserServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.dao.UserMapper;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import com.imooc.mall.util.MD5Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.security.NoSuchAlgorithmException;
/**
* UserService实现类
*/
@Service
//5.重写里面的方法 @Autowired引入一个mapper去查询数据库返回真正的信息 6返回UserController补全return
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser(){
//通过主键来查询一个对象
return userMapper.selectByPrimaryKey(1);
}
@Override
public void register(String userName, String password) throws ImoocMallException, NoSuchAlgorithmException {
// 16.查询用户名是否存在, 不允许重名 用userMapper去查数据
// 但未编写功能 去手动编写UserMapper.java
User result = userMapper.selectByName(userName);
if (result != null){
// 17. 用户已存在 在Service层不能直接return但是controller可以直接返回
// 创建一个异常类 com/imooc/mall/exception/ImoocMallException.java
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
// 通过检测 允许写入数据库
User user = new User();
user.setUsername(userName);
// user.setPassword(password);
//21.重写密码
user.setPassword(MD5Utils.getMD5Str(password));
//先判断是不是空 不是空才修改 18实现完以后回到controller层进行调用
int count = userMapper.insertSelective(user);
if (count==0){
throw new ImoocMallException(ImoocMallExceptionEnum.INSERT_FAILED);
}
}
}
登录功能分析
- 登录状态需要保持
- session的实现方案:登陆后,会保存用户信息到session
- 之后的访问,会先从session中获取用户信息,然后再执行业务逻辑
com/imooc/mall/common/Constant.java
package com.imooc.mall.common;
/**
* 21.SALT常量值 写完以后去UserServiceImpl重写密码代码
*/
public class Constant {
public static final String SALT = "aSp[PCx,aw.xq246}";
public static final String IMOOC_MALL_USER = "imooc_mall_user";
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
SYSTEM_ERROR(20000,"系统异常");
com/imooc/mall/controller/UserController.java
//22登录login接口的开发 23返回UserServiceImpl写login
@PostMapping("/login")
@ResponseBody
public ApiRestResponse login(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws ImoocMallException, NoSuchAlgorithmException {
if (StringUtils.isEmpty(userName)){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_USER_NAME);
}if (StringUtils.isEmpty(password)){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_PASSWORD_NAME);
}//26.编写完毕login接口
User user = userService.login(userName, password);
//保存用户信息时,不保存密码
user.setPassword(null);
//把对象放入session中
session.setAttribute(Constant.IMOOC_MALL_USER,user);
return ApiRestResponse.success(user);
}
com/imooc/mall/service/impl/UserServiceImpl.java
//23.写方法判断md5与其匹配
@Override
public User login(String userName, String password) throws ImoocMallException {
String md5Password = null;
try {
md5Password = MD5Utils.getMD5Str(password);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 23写方法判断md5与其匹配 UserMapper中写 login
//25.匹配 且去接口增加
User user = userMapper.selectLogin(userName, password);
if (user == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_PASSWORD);
}//能找到就返回用户 直接从上面写@Override再自动修复就可以自动生成login的接口
//26.返回UserController写完login接口
return user;
}
用户模块剩余接口开发 [更新个性签名]
com/imooc/mall/exception/ImoocMallExceptionEnum.java
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
NEED_LOGIN(10007,"用户未登录"),
UPDATE_FAILD(10008,"更新失败"),
SYSTEM_ERROR(20000,"系统异常");
}
com/imooc/mall/controller/UserController.java
//22登录login接口的开发 23返回UserServiceImpl写login
@PostMapping("/login")
@ResponseBody
public ApiRestResponse login(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws ImoocMallException, NoSuchAlgorithmException {
if (StringUtils.isEmpty(userName)) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_USER_NAME);
}
if (StringUtils.isEmpty(password)) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_PASSWORD_NAME);
}//26.编写完毕login接口 27更新个性签名接口
User user = userService.login(userName, password);
//保存用户信息时,不保存密码
user.setPassword(null);
//把对象放入session中 KEY
session.setAttribute(Constant.IMOOC_MALL_USER, user);
return ApiRestResponse.success(user);
}
// 27.个性签名接口 28编写UserServiceImpl的updateUserInfo方法
@PostMapping("/user/update")
@ResponseBody
public ApiRestResponse updateUserInfo(HttpSession session, @RequestParam String signature) throws ImoocMallException {
User currentUser = (User)session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_LOGIN);
}
User user = new User();
user.setId(currentUser.getId());
user.setPersonalizedSignature(signature);
//29.搞全代码
userService.updateInformation(user);
return ApiRestResponse.success();
}
com/imooc/mall/service/impl/UserServiceImpl.java
//23.写方法判断md5与其匹配
@Override
public User login(String userName, String password) throws ImoocMallException {
String md5Password = null;
try {
md5Password = MD5Utils.getMD5Str(password);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 23写方法判断md5与其匹配 UserMapper中写 login
//25.匹配 且去接口增加
User user = userMapper.selectLogin(userName, md5Password);
if (user == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_PASSWORD);
}//能找到就返回用户 直接从上面写@Override再自动修复就可以自动生成login的接口
//26.返回UserController写完login接口
return user;
}
// 28写updateUserInfo方法 不需要返回任何信息 只需要提醒更行成功即可
@Override
public void updateInformation(User user) throws ImoocMallException {
// 更新个性签名
int updateCount = userMapper.updateByPrimaryKeySelective(user);
if(updateCount>1){
throw new ImoocMallException(ImoocMallExceptionEnum.UPDATE_FAILD);
}
//快速使用@Override 快速补全接口代码 29返回UserController补全代码
}
com/imooc/mall/service/UserService.java
package com.imooc.mall.service;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.model.pojo.User;
import java.security.NoSuchAlgorithmException;
//5.这是抽象的接口 还要让它实现 再创建一个impl 实现接口类 UserServiceImpl.java
public interface UserService {
User getUser();
// 16.写完接口去实现接口UserServiceImpl.java
void register(String userName, String password) throws ImoocMallException, NoSuchAlgorithmException;
//23.写方法判断md5与其匹配
User login(String userName, String password) throws ImoocMallException;
// 28写updateUserInfo方法 不需要返回任何信息 只需要提醒更行成功即可
void updateInformation(User user) throws ImoocMallException;
}
退出登录
com/imooc/mall/controller/UserController.java
//30.退出登录接口
@PostMapping("/user/logout")
@ResponseBody
public ApiRestResponse logout(HttpSession session){
session.removeAttribute(Constant.IMOOC_MALL_USER);
return ApiRestResponse.success();
}
管理员接口
com/imooc/mall/controller/UserController.java
// 31.管理员登录接口 思路可以借鉴
@PostMapping("/adminLogin")
@ResponseBody
public ApiRestResponse adminLogin(@RequestParam("userName") String userName, @RequestParam("password") String password, HttpSession session) throws ImoocMallException, NoSuchAlgorithmException {
if (StringUtils.isEmpty(userName)) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_USER_NAME);
}
if (StringUtils.isEmpty(password)) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_PASSWORD_NAME);
}//31.拿到用户名和密码的时候 对其进行校验是否为管理员
//数据库中 role=1是普通用户 role=2是管理员用户
// 32去serviceImpl写一个方法
User user = userService.login(userName, password);
//33.判断是否为管理员
// userService.checkAdminRole(user).if
if (userService.checkAdminRole(user)) {
//是管理员
//保存用户信息时,不保存密码
user.setPassword(null);
//把对象放入session中 KEY
session.setAttribute(Constant.IMOOC_MALL_USER, user);
return ApiRestResponse.success(user);
}else {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_ADMIN);
}
// 34.创建分类接口 CategoryController
}
com/imooc/mall/service/impl/UserServiceImpl.java
// 32.拿到用户 role=1是普通用户 role=2是管理员用户
//33.回到Controller
@Override
public boolean checkAdminRole(User user){
return user.getRole().equals(2);
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名,注册失败"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
NEED_LOGIN(10007,"用户未登录"),
UPDATE_FAILD(10008,"更新失败"),
NEED_ADMIN(10009,"无管理员权限"),
SYSTEM_ERROR(20000,"系统异常");
}
总结用户模块
- 重难点:统一响应对象、登录状态保持、统一异常处理
- 常见错误:响应对象不规范、异常不统一处理
商品分类管理模块开发
什么是商品分类
- 条例清楚,层次分明
- 方便用户进行筛选和辨别
- 可以通过分类的设置快速的进入对应的商品列表页面进行商品选择
分类层级
- 在商品分类上需要继续做归类操作
- 分类设置成三级
- 层级太深的弊端:
- 一是对用户不太友好,不利于寻找
- 二是对后台管理人员不友好,不方便管理
分类模块的主要功能
模块介绍
开发添加分类接口part1
com/imooc/mall/controller/CategoryController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.model.AddCategoryReq;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.CategoryService;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
// 34.创建分类接口 CategoryController
@Controller
public class CategoryController {
@Autowired
UserService userService;
@Autowired
CategoryService categoryService;
@PostMapping("admin/category/add")
@ResponseBody
public ApiRestResponse addCategory(HttpSession session, @RequestBody AddCategoryReq addCategoryReq) {
//登录且必须为管理员才可以 参数中需要添加很多元素 可以使用封装来搞
//com/imooc/mall/model/model/AddCategoryReq.java
if (addCategoryReq.getName() == null || addCategoryReq.getType() == null || addCategoryReq.getParentId() == null || addCategoryReq.getOrderNum() == null) {
return ApiRestResponse.error(ImoocMallExceptionEnum.PARA_NOT_NULL);
}
//对身份进行校验 用session获取当前的用户
User currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null){
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_LOGIN);
}//校验管理员 userService写过直接引用过来
boolean adminRole = userService.checkAdminRole(currentUser);
if (adminRole){ //35.创建一个CategoryService 分类目录Service
//37.补全代码 是管理员 在上面添加@RequestBody 去postman Body->(raw/JSON)里测试接口
categoryService.add(addCategoryReq);
return ApiRestResponse.success();
}else {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_ADMIN);
}
//38.简化参数校验过程 || || || || @Valid @Size(AddCategoryReq) @NotNull
//39.增加GlobalExceptionHandler.java中的提示代码 不仅仅只提供20000 系统异常 handleMethodArg。。。
}
}
com/imooc/mall/model/model/AddCategoryReq.java
package com.imooc.mall.model.model;
/**
* 34.AddCategoryReq 新建一个类 供添加接口进行封装 回去引用
*/
public class AddCategoryReq {
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
}+GETTER SETTER
com/imooc/mall/service/CategoryService.java
package com.imooc.mall.service;
import com.imooc.mall.model.model.AddCategoryReq;
import com.imooc.mall.model.pojo.Category;
//35.创建一个CategoryService 分类目录Service 36创建一个它的实现类CategoryServiceImpl
public interface CategoryService {
void add(AddCategoryReq addCategoryReq);
}
com/imooc/mall/service/impl/CategoryServiceImpl.java
package com.imooc.mall.service.impl;
import com.fasterxml.jackson.databind.util.BeanUtil;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.dao.CategoryMapper;
import com.imooc.mall.model.model.AddCategoryReq;
import com.imooc.mall.model.pojo.Category;
import com.imooc.mall.service.CategoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 36.目录分类Service实现类
*/
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
// 去Mapper里增加selectbyName 117行
CategoryMapper categoryMapper;
public void add(AddCategoryReq addCategoryReq) {
Category category = new Category();
// category.setName(addCategoryReq.getName());
//字段类型一样 字段名一样的话可以自动拷贝进去
BeanUtils.copyProperties(addCategoryReq,category);
Category categoryOld = categoryMapper.selectByName(addCategoryReq.getName());
if (categoryOld != null){ //重名目录 不允许创建 (优化)将ImoocMallException中的extends换一个
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
int count = categoryMapper.insertSelective(category);
if (count == 0){
throw new ImoocMallException(ImoocMallExceptionEnum.NEED_ADMIN);
}//37.回到CategoryController.java
}
}
mappers/CategoryMapper.xml
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from imooc_mall_category
where name = #{name,jdbcType=VARCHAR}
</select>
参数校验
注解 |
说明 |
@Valid |
需要验证 |
@NotNull |
非空 |
@Max(value) |
最大值 |
@Size(max, min) |
字符串长度范围限制 |
com/imooc/mall/model/model/AddCategoryReq.java
package com.imooc.mall.model.model;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 34.AddCategoryReq 新建一个类 供添加接口进行封装 回去引用
*/
public class AddCategoryReq {
@Size(min=2,max=5)
private String name;
@NotNull
@Max(3)
private Integer type;
@NotNull(message = "parentId不能为null")
private Integer parentId;
@NotNull
private Integer orderNum;
===================================================
POST:127.0.0.1:8083/admin/category/add
Body→raw→JSON:{"name":"鸭货伴手零食","type":2,"parentId":6,"orderNum":10}
{
"status": 20000,
"msg": "系统异常",
"data": null
}
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [1] in public com.imooc.mall.common.ApiRestResponse com.imooc.mall.controller.CategoryController.addCategory(javax.servlet.http.HttpSession,com.imooc.mall.model.model.AddCategoryReq): [Field error in object 'addCategoryReq' on field 'name': rejected value [鸭货伴手零食]; codes [Size.addCategoryReq.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addCategoryReq.name,name]; arguments []; default message [name],5,2]; default message [个数必须在2和5之间]]
com/imooc/mall/exception/GlobalExceptionHandler.java
package com.imooc.mall.exception;
import com.imooc.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
/**
* 19.处理统一异常的handler 业务异常 处理不同逻辑异常 20对密码进行MD5加密UserServiceImpl 先创建MD5Utils
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 统一处理Exception.class异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception e) {
log.error("Default Exception: ", e);
return ApiRestResponse.error(ImoocMallExceptionEnum.SYSTEM_ERROR);
}
@ExceptionHandler(ImoocMallException.class)
@ResponseBody
public Object handleImoocMallException(ImoocMallException e) {
log.error("ImoocMallException: ", e); //传进来的是什么就传出去
return ApiRestResponse.error(e.getCode(), e.getMessage());
}
// 39.处理方法参数不合规的情况
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ApiRestResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException: ", e);
return handleBindingResult(e.getBindingResult());
}
// 40.处理返回异常的ApiRespond 41去pom引入Swagger自动生成API文档
private ApiRestResponse handleBindingResult(BindingResult result){
// 把异常处理为对外暴露的提示
List<String> list = new ArrayList<>();
if (result.hasErrors()){
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError objectError : allErrors) { //itli快速 对着for按alt+回车 改成增强for
String message = objectError.getDefaultMessage();
list.add(message);
}
}
if (list.size() == 0){
return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR);
} //list.toString()生成所创建的异常描述信息
return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR.getCode(), list.toString());
}
}
Swagger自动生成API文档
pom.xml
<!--41.导入Swagger自动生成API文档 并在main函数中加入注解 @EnableSwagger2-->
<!--42.再去创造一个config 配置文件 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
com/imooc/mall/config/SpringFoxConfig.java
package com.imooc.mall.config;
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.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SpringFoxConfig {
//41.访问http://localhost:8083/swagger-ui.html可以看到API文档
//42创建ImoocMallWebMvcConfig
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("慕慕生鲜")
.description("")
.termsOfServiceUrl("")
.build();
}
}
com/imooc/mall/MallApplication.java
package com.imooc.mall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.mall.model.dao")
@EnableSwagger2
public class MallApplication {
public static void main(String[] args) {
SpringApplication.run(MallApplication.class, args);
}
}
com/imooc/mall/config/ImoocMallWebMvcConfig.java
package com.imooc.mall.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 42.配置地址映射
* 43去CategoryController.java 加一个 @ApiOperation("后台添加目录")
44.新增一个目录的updateCategory的参数 [UpdateCategoryReq.java ]
*/
@Configuration //代表是一个配置
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
public void addResourceHandles(ResourceHandlerRegistry registry){
// 把地址给到对应的目录下
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars");
}
}
==================================================
http://localhost:8083/swagger-ui.html
更新目录接口
com/imooc/mall/model/request/UpdateCategoryReq.java
package com.imooc.mall.model.request;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 45.CategoryController.java中创建新增方法
*/
public class UpdateCategoryReq {
@NotNull(message = "id不能为null")
private Integer id;
@Size(min=2,max=5)
private String name;
@Max(3)
private Integer type;
private Integer parentId;
private Integer orderNum;
com/imooc/mall/controller/CategoryController.java
// 45.创建新增接口 46在CategoryServiceImpl.java中新增更新方法
@ApiOperation("后台更新目录")
@PostMapping("admin/category/update")
@ResponseBody
public ApiRestResponse updateCategory( @Valid @RequestBody UpdateCategoryReq updateCategoryReq, HttpSession session) {
//对身份进行校验 用session获取当前的用户
User currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_LOGIN);
}//校验管理员 userService写过直接引用过来
boolean adminRole = userService.checkAdminRole(currentUser);
if (adminRole) {
//补全代码 是管理员 在上面添加@RequestBody 去postman Body->(raw/JSON)里测试接口
//46.补全接口代码
Category category = new Category(); //复制过来
BeanUtils.copyProperties(updateCategoryReq, category);
categoryService.update(category);
return ApiRestResponse.success();
} else {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_ADMIN);
}//47.为了统一接口校验管理员身份 NEED_LOGIN NEED_ADMIN
// 创建一个com/imooc/mall/filter/AdminFilter.java
}
com/imooc/mall/service/impl/CategoryServiceImpl.java
@Override //47.快速生成
// 46在CategoryServiceImpl.java中新增更新方法
public void update(Category updateCategory){
if (updateCategory.getName() != null){
Category categoryOld = categoryMapper.selectByName(updateCategory.getName());
if (categoryOld != null && !categoryOld.getId().equals(updateCategory.getId())){ //不能为空且和原来的名字不一样
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
}
categoryMapper.updateByPrimaryKeySelective(updateCategory); //根据主键更新
// 不和其他名字冲突
}
com/imooc/mall/service/CategoryService.java
package com.imooc.mall.service;
import com.imooc.mall.model.pojo.Category;
import com.imooc.mall .model.request.AddCategoryReq;
//35.创建一个CategoryService 分类目录Service 36创建一个它的实现类CategoryServiceImpl
public interface CategoryService {
void add(AddCategoryReq addCategoryReq);
//47.快速生成
// 46在CategoryServiceImpl.java中新增更新方法
void update(Category updateCategory);
}
统一校验管理员身份
com/imooc/mall/controller/CategoryController.java
// 45.创建新增接口 46在CategoryServiceImpl.java中新增更新方法
@ApiOperation("后台更新目录")
@PostMapping("admin/category/update")
@ResponseBody
public ApiRestResponse updateCategory( @Valid @RequestBody UpdateCategoryReq updateCategoryReq, HttpSession session) {
//对身份进行校验 用session获取当前的用户
User currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null) {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_LOGIN);
}//校验管理员 userService写过直接引用过来
boolean adminRole = userService.checkAdminRole(currentUser);
if (adminRole) {
//补全代码 是管理员 在上面添加@RequestBody 去postman Body->(raw/JSON)里测试接口
//46.补全接口代码
Category category = new Category(); //复制过来
BeanUtils.copyProperties(updateCategoryReq, category);
categoryService.update(category);
return ApiRestResponse.success();
} else {
return ApiRestResponse.error(ImoocMallExceptionEnum.NEED_ADMIN);
}//47.为了统一接口校验管理员身份 NEED_LOGIN NEED_ADMIN
// 创建一个com/imooc/mall/filter/AdminFilter.java
}
@ApiOperation("后台更新目录")
@PostMapping("admin/category/update")
@ResponseBody //没有在接口里做权限校验
public ApiRestResponse deleteCategory(){
return null;
}
com/imooc/mall/filter/AdminFilter.java
package com.imooc.mall.filter;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.pojo.Category;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
*
* 47. 管理员校验过滤器 48.Admin过滤器的配置
*/
public class AdminFilter implements Filter {
@Autowired
UserService userService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//对身份进行校验 用session获取当前的用户
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpSession session = request.getSession();
User currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null) {
PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
out.write("{\n" //用户未登录
+ " \"status\": 10007,\n"
+ " \"msg\": \"NEED_LOGIN\",\n"
+ " \"data\": null\n"
+ "}");
out.flush();
out.close();
return;
}//校验管理员 userService写过直接引用过来
boolean adminRole = userService.checkAdminRole(currentUser);
if (adminRole) { //放行代码
filterChain.doFilter(servletRequest,servletResponse);
} else {
PrintWriter out = new HttpServletResponseWrapper(
(HttpServletResponse) servletResponse).getWriter();
out.write("{\n"
+ " \"status\": 10009,\n"
+ " \"msg\": \"NEED_ADMIN\",\n"
+ " \"data\": null\n"
+ "}");
out.flush();
out.close();
}
}
@Override
public void destroy() {
}
}
com/imooc/mall/filter/AdminFilterConfig.java
package com.imooc.mall.filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Admin过滤器的配置
*/
@Configuration
public class AdminFilterConfig {
@Bean
public AdminFilter adminFilter(){
return new AdminFilter();
}
@Bean(name = "adminFilterConf")
public FilterRegistrationBean adminFilterConfig(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(adminFilter());
filterRegistrationBean.addUrlPatterns("/admin/category/*");
filterRegistrationBean.addUrlPatterns("/admin/product/*");
filterRegistrationBean.addUrlPatterns("/admin/order/*");
filterRegistrationBean.setName("adminFilterConfig");
return filterRegistrationBean;
}
}
com/imooc/mall/filter/AdminFilterConfig.java
删除目录接口、分页功能开发
@RequestBody用来接收http post请求的body,前端传入序列化好的json数据,后端可以解析为json对象(Content-Type需要指定为 application/json)。
@RequestParam用来接收请求url?后面的参数,或者Content-Type为multipart/form-data、application/x-www-form-urlencoded时的http body数据。
com/imooc/mall/controller/CategoryController.java
@ApiOperation("后台删除目录")
@PostMapping("admin/category/delete")
@ResponseBody //没有在接口里做权限校验
//49.编写delete接口 再去CategoryServiceImpl.java 写delete方法
public ApiRestResponse deleteCategory(@RequestParam Integer id) {
categoryService.delete(id);
return ApiRestResponse.success();
}
com/imooc/mall/service/impl/CategoryServiceImpl.java
//49.写delete方法 @Override快速更新service
@Override
public void delete(Integer id){
Category categoryOld = categoryMapper.selectByPrimaryKey(id);
//查不到记录,无法删除,删除失败
if (categoryOld == null){
throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
}
int count = categoryMapper.deleteByPrimaryKey(id);
if (count == 0){
throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
}
}
com/imooc/mall/controller/CategoryController.java
//50.写后台查询商品分类列表的接口
@ApiOperation("后台目录列表")
@PostMapping("admin/category/list")
@ResponseBody //没有在接口里做权限校验
public ApiRestResponse listCategoryForAdmin(@RequestParam Integer pageNum, @RequestParam Integer pageSize){
//51.去CategoryServiceImpl编写该接口的实现类
PageInfo pageInfo = categoryService.listForAdmin(pageNum, pageSize);
return ApiRestResponse.success(pageInfo);
}
com/imooc/mall/model/vo/CategoryVO.java
package com.imooc.mall.model.vo;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class CategoryVO {
private Integer id;
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
private Date createTime;
private Date updateTime;
private List<CategoryVO> childCategory = new ArrayList<>();
}Setter+Getter
com/imooc/mall/service/impl/CategoryServiceImpl.java
//51.创建vo[转换过后反应给前端的一个类] pageInfo里面蕴藏着一个List<Category>
@Override
public PageInfo listForAdmin(Integer pageNum, Integer pageSize){
//把分页的功能包裹在List后 而不是直接返回
//52.引入分页查询的pom 续写分页代码
PageHelper.startPage(pageNum,pageSize,"type,order_num");
//53.写一个查询的mapper新的sql语句 CategoryMapper.java => List<Category> selectList();
//54.返回Categorycontroller补全代码
List<Category> categoryList = categoryMapper.selectList();
PageInfo pageInfo = new PageInfo(categoryList);
return pageInfo;
}
pom.xml
<!-- 52.引入分页的依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
用户分类列表接口开发
com/imooc/mall/controller/CategoryController.java
//54.用户分类列表接口开发 去Impl补写实现类
@ApiOperation("前台目录列表")
@GetMapping("category/list")
@ResponseBody
public ApiRestResponse listCategoryForCustomer(){
List<CategoryVO> categoryVOS = categoryService.listCategoryForCustomer();
return ApiRestResponse.success(categoryVOS);
}
============================================
目录列表(給用户看):127.0.0.1:8083/category/list
com/imooc/mall/service/impl/CategoryServiceImpl.java
//54.用户分类列表接口开发
@Override
public List<CategoryVO> listCategoryForCustomer(){
ArrayList<CategoryVO> categoryVOList = new ArrayList<>();
recursivelyFindCategories(categoryVOList, 0);
//55.去Mapper.java 和 Mapper.xml写方法
return categoryVOList;
}
private void recursivelyFindCategories(List<CategoryVO> categoryVOList, Integer parentId){
//递归获取所有子类别并组合 合成一个"目录树"
List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId);
if (!CollectionUtils.isEmpty(categoryList)){
//空 或 有无元素 itli
for (int i = 0; i < categoryList.size(); i++) {
Category category = categoryList.get(i);
CategoryVO categoryVO = new CategoryVO();
//拷贝链接 比原来的多一个childCategory这个字段未被赋值
BeanUtils.copyProperties(category, categoryVO);
categoryVOList.add(categoryVO);
//拿到childCategory字段并赋值 再从上面return categoryVOList;
recursivelyFindCategories(categoryVO.getChildCategory(), categoryVO.getId());
}
}
}
com/imooc/mall/model/dao/CategoryMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Category;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CategoryMapper {
int deleteByPrimaryKey(Integer id);
int insert(Category record);
int insertSelective(Category record);
Category selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Category record);
int updateByPrimaryKey(Category record);
Category selectByName(String name);
List<Category> selectList();
List<Category> selectCategoriesByParentId(Integer parentId);
}
mappers/CategoryMapper.xml
<mapper>
<select id="selectList" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/>
from imooc_mall_category
</select>
<select id="selectCategoriesByParentId" resultMap="BaseResultMap" parameterType="int">
select <include refid="Base_Column_List"/>
from imooc_mall_category
where parent_id = #{parentId}
</select>
</mapper>
利用Redis缓存加速响应 [目录变化频繁(访问量大的) 增加效率]
pom.xml
<!-- 55.导入Redis的pom文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
56.增加一个@EnableCaching注解让idea知道你想打开缓存功能
com/imooc/mall/MallApplication.java
package com.imooc.mall;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.mall.model.dao")
@EnableSwagger2
@EnableCaching
//56.增加注解 并去想用的前面加注解 @Cacheable(value = "listCategoryForCustomer")
public class MallApplication {
public static void main(String[] args) {
SpringApplication.run(MallApplication.class, args);
}
}
import org.springframework.cache.annotation.Cacheable; [对的]
import springfox.documentation.annotations.Cacheable; [错的]
com/imooc/mall/service/impl/CategoryServiceImpl.java
//这里要格外注意 不要导错包 不然运行不了redis 里面的keys会没有键值对
// import org.springframework.cache.annotation.Cacheable; [对的]
// import springfox.documentation.annotations.Cacheable; [错的]
//54.用户分类列表接口开发
@Override //56.在下方加想用Redis的注解
//57.创建一个对于缓存的配置类com/imooc/mall/config/CachingConfig.java
@Cacheable(value = "listCategoryForCustomer")
public List<CategoryVO> listCategoryForCustomer(){
ArrayList<CategoryVO> categoryVOList = new ArrayList<>();
recursivelyFindCategories(categoryVOList, 0);
//55.去Mapper.java 和 Mapper.xml写方法
return categoryVOList;
}
private void recursivelyFindCategories(List<CategoryVO> categoryVOList, Integer parentId){
//递归获取所有子类别并组合 合成一个"目录树"
List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId);
if (!CollectionUtils.isEmpty(categoryList)){
//空 或 有无元素 itli
for (int i = 0; i < categoryList.size(); i++) {
Category category = categoryList.get(i);
CategoryVO categoryVO = new CategoryVO();
//拷贝链接 比原来的多一个childCategory这个字段未被赋值
BeanUtils.copyProperties(category, categoryVO);
categoryVOList.add(categoryVO);
//拿到childCategory字段并赋值 再从上面return categoryVOList;
recursivelyFindCategories(categoryVO.getChildCategory(), categoryVO.getId());
}
}
}
com/imooc/mall/config/CachingConfig.java
package com.imooc.mall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import java.time.Duration;
/**
* 57.缓存的配置类 想要运行成功保存序列化 要去弄个序列化接口com/imooc/mall/model/vo/CategoryVO.java
*/
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter
.lockingRedisCacheWriter(connectionFactory);
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30));
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
cacheConfiguration);
return redisCacheManager;
}
}
com/imooc/mall/model/vo/CategoryVO.java
public class CategoryVO implements Serializable {
private Integer id;
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
private Date createTime;
private Date updateTime;
private List<CategoryVO> childCategory = new ArrayList<>();
}Setter+Getter
Idea调试技巧
- 断点统一开关
- 条件断点
- 单步调试
- step into 会进入方法内部
- step out 跳出方法
- 表达式求值 [Evaluate Expression]
总结商品分类模块
- 重难点:参数校验、Swagger[API文档自动]、统一鉴权[Filter过滤器]、Redis整合[Springboot整合]、调试功能
- 常见错误:参数手动校验、项目没用Redis缓存、不善用调试
商品模块介绍
演示、数据表设计、接口设计
新增商品功能开发
com\imooc\mall\controller\ProductAdminController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import org.springframework.stereotype.Controller;
/**
* 58.后台商品管理Controller pojo的product复制一份到request变成AddProductReq
59.需要ProductService.java
*/
@Controller
public class ProductAdminController {
@Autowired
ProductService productService;
@PostMapping("admin/product/add")
public ApiRestResponse addProduct(@Valid @RequestBody AddProductReq addProductReq){
//61.补全代码 目前图片上传还未开发成功
productService.add(addProductReq);
return ApiRestResponse.success();
}
}
com/imooc/mall/model/request/AddProductReq.java
package com.imooc.mall.model.request;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class AddProductReq {
@NotNull(message = "商品名称不能为null")
private String name;
@NotNull(message = "商品图片不能为null")
private String image;
private String detail;
@NotNull(message = "商品分类不能为null")
private Integer categoryId;
@NotNull(message = "商品价格不能为null")
@Min(value = 1, message = "价格不能为null")
private Integer price;
@NotNull(message = "商品库存不能为null")
@Max(value = 10000, message = "库存不能大于10000")
private Integer stock;
private Integer status;
}Getter+Setter
com/imooc/mall/service/ProductService.java
package com.imooc.mall.service;
import com.imooc.mall.model.request.AddProductReq;
/**
* 59.商品Service 再创建一个实现类ProductServiceImpl
*/
public interface ProductService {
void add(AddProductReq addProductReq);
}
com/imooc/mall/service/impl/ProductServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.dao.ProductMapper;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.request.AddProductReq;
import com.imooc.mall.service.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 59.商品服务实现类
*/
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
ProductMapper productMapper;//60.ProductMapper SQL通过名字查找product是否存在 151行
@Override
public void add(AddProductReq addProductReq){
Product product = new Product();
BeanUtils.copyProperties(addProductReq, product);//赋值
//61.商品重名情况 返回ProductAdminController补全代码
//62.图片上传接口 ProductAdminController
Product productOld = productMapper.selectByName(addProductReq.getName());
if (productOld != null){
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
int count = productMapper.insertSelective(product);
if (count == 0){
throw new ImoocMallException(ImoocMallExceptionEnum.CREATE_FAILED);
}
}
}
图片上传
- 文件名UUID
- 通用唯一识别码(Universally Unique Identifier)
- 防止重名、防止爬图
- 生成规则:日期和时间、MAC地址、HashCode、随机数
com/imooc/mall/controller/ProductAdminController.java
//62.图片上传接口
@PostMapping("admin/upload/file")
public ApiRestResponse upload(HttpServletRequest httpServletRequest, @RequestParam("file") MultipartFile file){
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf("."));//用文件后面的名字
//生成文件名称UUID
UUID uuid = UUID.randomUUID();
String newFileName = uuid.toString() + suffixName;
//创建文件 放在常量类中 Constant.java
//63.application.properties配置file.upload.dir
// new File()
File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);
File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
if (!fileDirectory.exists()){
if (!fileDirectory.mkdir()){//新建文件夹
throw new ImoocMallException(ImoocMallExceptionEnum.MKDIR_FAILED);
}
}
try {
file.transferTo(destFile); //传进来的写到空的方法中去
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
return ApiRestResponse.success(getHost(new URI(httpServletRequest.getRequestURL()+""))+"/images/"+newFileName); //生成的路径IP和端口号
} catch (URISyntaxException e) {
return ApiRestResponse.error(ImoocMallExceptionEnum.UPLOAD_FAILED);
}
}
private URI getHost(URI uri){
URI effectiveURI;
try {
effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),null,null,null);
} catch (URISyntaxException e) {
effectiveURI = null; //如果新建失败 就返回回去
}
return effectiveURI;
}
com/imooc/mall/common/Constant.java
public class Constant {
public static final String SALT = "aSp[PCx,aw.xq246}";
public static final String IMOOC_MALL_USER = "imooc_mall_user";
@Value("${file.upload.dir}")
public static String FILE_UPLOAD_DIR;
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
NEED_LOGIN(10007,"用户未登录"),
UPDATE_FAILD(10008,"更新失败"),
NEED_ADMIN(10009,"无管理员权限"),
NAME_NOT_NULL(10010,"参数不能为空"),
PARA_NOT_NULL(10011,"参数不能为空"),
CREATE_FAILED(10012,"新增失败"),
REQUEST_PARAM_ERROR(10013,"参数错误"),
DELETE_FAILED(10014,"删除失败"),
MKDIR_FAILED(10015,"文件夹创建失败"),
UPLOAD_FAILED(10015,"图片上传失败"),
SYSTEM_ERROR(20000,"系统异常");
}
资源映射开发
此时从postman中创建一个上传图片的接口
127.0.0.1:8083/admin/upload/file
选择Body→form-data
此时上传图片会报错 {
“status”: 20000,
“msg”: “系统异常”,
“data”: null
}
com/imooc/mall/common/Constant.java
package com.imooc.mall.common;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 21.SALT常量值 写完以后去UserServiceImpl重写密码代码
*/
@Component //让spring帮注入value
public class Constant {
public static final String SALT = "aSp[PCx,aw.xq246}";
public static final String IMOOC_MALL_USER = "imooc_mall_user";
public static String FILE_UPLOAD_DIR;
//64.为了解决上传图片系统异常报错 注入失败的原因是上方是static普通变量 set方法把静态变量赋值
@Value("${file.upload.dir}")
public void setFileUploadDir(String fileUploadDir){
FILE_UPLOAD_DIR = fileUploadDir;
}//65.打开ImoocMallWebMvcConfig 加一个映射规则
}
自定义静态资源映射目录
- 上传图片后回显
- 配置SpringBootWebMvcConfig
- 静态资源到本地目录的映射
- 演示打开图片
com/imooc/mall/config/ImoocMallWebMvcConfig.java
package com.imooc.mall.config;
import com.imooc.mall.common.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 42.配置地址映射 43去CategoryController.java 加一个 @ApiOperation("后台添加目录")
* 44.新增一个目录的updateCategory的参数 [UpdateCategoryReq.java ]
*/
@Configuration //代表是一个配置
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
public void addResourceHandles(ResourceHandlerRegistry registry){
registry.addResourceHandler("/images/**").addResourceLocations("file:" + Constant.FILE_UPLOAD_DIR);
// 把地址给到对应的目录下
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars");
}
}
更新(UpdateProductReq)和新增(AddProductReq)商品
com/imooc/mall/model/request/UpdateProductReq.java
public class UpdateProductReq {
@NotNull
private Integer id;
private String name;
private String image;
private String detail;
private Integer categoryId;
@Min(value = 1, message = "价格不能为null")
private Integer price;
@Max(value = 10000, message = "库存不能大于10000")
private Integer stock;
private Integer status;
}
com/imooc/mall/config/ImoocMallWebMvcConfig.java
package com.imooc.mall.config;
import com.imooc.mall.common.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 42.配置地址映射 43去CategoryController.java 加一个 @ApiOperation("后台添加目录")
* 44.新增一个目录的updateCategory的参数 [UpdateCategoryReq.java ]
*/
@Configuration //代表是一个配置
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
public void addResourceHandles(ResourceHandlerRegistry registry){
//65.增加一个registry 66.新增接口继续开发 ProductAdminController
registry.addResourceHandler("/images/**").addResourceLocations("file:" + Constant.FILE_UPLOAD_DIR);
// 把地址给到对应的目录下
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars");
}
}
com/imooc/mall/controller/ProductAdminController.java
//66. 接口 复制一个request中的AddProductReq 之后回来补全代码
@ApiOperation("后台更新商品")
@PostMapping("/admin/product/update")
public ApiRestResponse updateProduct(@Valid @RequestBody UpdateProductReq updateProductReq){
Product product = new Product();
BeanUtils.copyProperties(updateProductReq, product);
//67.进入ProductServiceImpl编写接口实现类
productService.update(product);
return ApiRestResponse.success();
}
//68. 搞一个删除的接口 同理也在ProductServiceImpl中写实现类 之后回来补全代码
@ApiOperation("后台删除商品")
@PostMapping("/admin/product/delete")
public ApiRestResponse deleteProduct(@RequestParam Integer id){
productService.delete(id);
return ApiRestResponse.success();
}com/imooc/mall/controller/ProductAdminController.java
com/imooc/mall/service/impl/ProductServiceImpl.java
//67.写updateProduct实现类 @Override自动导入
@Override
public void update(Product updateProduct) {
Product productOld = productMapper.selectByName(updateProduct.getName());
//同名且不同id,不能继续修改
if (productOld != null && productOld.getId().equals(updateProduct.getId())) {
throw new ImoocMallException(ImoocMallExceptionEnum.NAME_EXISTED);
}
int count = productMapper.updateByPrimaryKeySelective(updateProduct);
if (count == 0) {
throw new ImoocMallException(ImoocMallExceptionEnum.UPDATE_FAILD);
}
}
//68.删除实现类 @Override自动导入
@Override
public void delete(Integer id) {
Product productOld = productMapper.selectByPrimaryKey(id);
//查不到该记录,无法删除
if (productOld == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
}
int count = productMapper.deleteByPrimaryKey(id);
if (count == 0) {
throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
}
}
批量上下架
com/imooc/mall/controller/ProductAdminController.java
//69. 批量上下架接口 同理也在ProductServiceImpl中写实现类 Napper中增加批量上下架的SQL
// 之后回来补全代码
@ApiOperation("后台批量上下架接口")
@PostMapping("/admin/product/batchUpdateSellStatus")
public ApiRestResponse batchUpdateSellStatus(@RequestParam Integer[] ids, @RequestParam Integer sellStatus){
productService.batchUpdateSellStatus(ids,sellStatus);
return ApiRestResponse.success();
}
=====================================================
127.0.0.1:8083//admin/product/batchUpdateSellStatus?ids=2,3&sellStatus=0
com/imooc/mall/service/impl/ProductServiceImpl.java
//69.去ProductAdminController增加batchUpdateSellStatus接口
@Override
public void batchUpdateSellStatus(Integer[] ids, Integer sellStatus){
productMapper.batchUpdateSellStatus(ids, sellStatus);
return null;
}
com/imooc/mall/model/dao/ProductMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Product;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductMapper {
int deleteByPrimaryKey(Integer id);
int insert(Product record);
int insertSelective(Product record);
Product selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Product record);
int updateByPrimaryKey(Product record);
Product selectByName(String name);
int batchUpdateSellStatus(@Param("ids") Integer[] ids, @Param("sellStatus") Integer sellStatus);
}
mappers/ProductMapper.xml
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/>
from imooc_mall_product
where name = #{name,jdbcType = VARCHAR}
</select>
<update id="batchUpdateSellStatus">
update imooc_mall_product
set status=#{sellStatus}
where id in
<foreach collection="ids" close=")" item="id" open="(" separator=",">
#{id}
</foreach>
</update>
后台商品列表、商品详情接口
com/imooc/mall/service/impl/ProductServiceImpl.java
//70.后台商品列表接口
@Override
public PageInfo listForAdmin(Integer pageNum, Integer pageSize){
PageHelper.startPage(pageNum, pageSize); //在mapper里写查询sql
List<Product> products = productMapper.selectListForAdmin();
PageInfo pageInfo = new PageInfo(products);
return pageInfo;
}
//72.商品详情接口开发 再去ProductController调用
@Override
public Product detail(Integer id){
Product product = productMapper.selectByPrimaryKey(id);
return product;
}
com/imooc/mall/controller/ProductAdminController.java
//70.后台商品列表接口 同理也在ProductServiceImpl中写实现类 Napper中增加批量上下架的SQL 补全代码
//71.开发与前台商品ProductController.java
@ApiOperation("后台商品列表接口")
@PostMapping("/admin/product/list")
public ApiRestResponse list(@RequestParam Integer[] pageNum, @RequestParam Integer pageSize){
PageInfo pageInfo = productService.batchUpdateSellStatus(pageNum,pageSize);
return ApiRestResponse.success(pageInfo);
}
com/imooc/mall/controller/ProductController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.service.ProductService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 71.前台商品Controller 72新增detail接口 ProductServiceImpl.java
*/
@RestController
public class ProductController {
@Autowired
ProductService productService;
@ApiOperation("商品详情")
@GetMapping("product/detail")
public ApiRestResponse detail(@RequestParam Integer id){
Product product = productService.detail(id);
return ApiRestResponse.success(product);
}
}
前台商品列表接口part1
商品列表:搜索功能
入参判空 → 加%通配符 → like关键字
对于查询目录的in处理
- 目录处理:如果查某个目录下的商品,不仅是需要查出来该目录的,还需要查出来子目录的所有商品
- 所以这里要拿到某一个目录Id下的所有子目录id的List
前台:商品列表
- 排序功能
- MyBatis PageHelper
- 枚举:order by [自定义]
com/imooc/mall/service/impl/ProductServiceImpl.java
//72.商品详情接口开发 再去ProductController调用
@Override
public Product detail(Integer id){
Product product = productMapper.selectByPrimaryKey(id);
return product;
}
//73.完成实现类
@Override
public PageInfo list(ProductListReq productListReq){
//复杂查询就构建一个querry对象 ProductListQuery.java
ProductListQuery productListQuery = new ProductListQuery();
//☆☆搜索处理☆☆ 拼接且转换成字符串去数据库查找
if (!StringUtils.isEmpty(productListReq.getKeyword())){
String keyword = new StringBuilder().append("%").append(productListReq.getKeyword()).append("%").toString();
productListQuery.setKeyword(keyword);
}
//☆☆目录处理☆☆:如果查某个目录下的商品,不仅是需要查出该目录下的,还要把所有子目录的所有商品都查出来,所以要拿到一个目录id的List
if (productListReq.getCategoryId() != null){
//要拿到子目录 引用CategoryService CategoryVO原本是给前台目录用的 需要重构一下 不是所有目录而是指定目录
//改动代码 CategoryServiceImpl.java中的listCategoryForCustomer 传入的参数是Integer parentId
//productListReq获取了所有根节点的list 因为点开List<CategoryVO>里面包括一个 private List<CategoryVO>递归结构
List<CategoryVO> categoryVOList = categoryService.listCategoryForCustomer(productListReq.getCategoryId());
ArrayList<Integer> categoryIds = new ArrayList<>(); //拿过来之后存储的list
categoryIds.add(productListReq.getCategoryId());
getCategoryIds(categoryVOList,categoryIds);
productListQuery.setCategoryIds(categoryIds);
}
//74.排序能力 错误:前端传什么就 就传到sql中排序 这样是不安全的 要提前处理好
//☆☆排序处理☆☆:去Constant定义支持的排序模式和手段
String orderBy = productListReq.getOrderBy();
if (Constant.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize(), orderBy);
}else {//前端不一定包含数据的话就不排序了
PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize());
}//75.去写Mapper 最后回到ProductController写调用
List<Product> productList = productMapper.selectList(productListQuery);
PageInfo pageInfo = new PageInfo(productList);
return pageInfo;
}
//74.写一个方法拿到所有的id 拿到参数之后往哪里存放
private void getCategoryIds(List<CategoryVO>categoryVOList, ArrayList<Integer> categoryIds){
for (int i = 0; i < categoryVOList.size(); i++) {
CategoryVO categoryVO = categoryVOList.get(i);
if (categoryVO != null){
categoryIds.add(categoryVO.getId());
//递归子节点 子子节点
getCategoryIds(categoryVO.getChildCategory(), categoryIds); //去上面调用方法 传入对象
}
}
}
com/imooc/mall/model/query/ProductListQuery.java
package com.imooc.mall.model.query;
import java.util.List;
/**
* 查询商品列表的Query
*/
public class ProductListQuery {
private String keyword;
private List<Integer> categoryIds;
}Getter+Setter
com/imooc/mall/model/dao/ProductMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.query.ProductListQuery;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductMapper {
List<Product> selectListForAdmin(); //给前台用户用的
List<Product> selectList(@Param("ids")ProductListQuery query); //给后台用户用的 Type是一个类
}
mappers/ProductMapper.xml
<select id="selectList" resultMap="BaseResultMap"
parameterType="com.imooc.mall.model.query.ProductListQuery">
select
<include refid="Base_Column_List"/>
from imooc_mall_product
<where>
<if test="query.keyword != null">
and name like #{query.keyword}
</if>
<if test="query.categoryIds != null">
and category_id in
<foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">
#{item}
</foreach>
</if>
and status = 1
</where>
order by update_time desc
</select>
com/imooc/mall/model/dao/ProductMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.query.ProductListQuery;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductMapper {
int deleteByPrimaryKey(Integer id);
int insert(Product record);
int insertSelective(Product record);
Product selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Product record);
int updateByPrimaryKey(Product record);
Product selectByName(String name);
int batchUpdateSellStatus(@Param("ids") Integer[] ids, @Param("sellStatus") Integer sellStatus);
List<Product> selectListForAdmin(); //给前台用户用的
List<Product> selectList(@Param("query")ProductListQuery query); //给后台用户用的 Type是一个类
}
com/imooc/mall/controller/ProductController.java
package com.imooc.mall.controller;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.request.ProductListReq;
import com.imooc.mall.service.ProductService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 71.前台商品Controller 72新增detail接口 ProductServiceImpl.java
*/
@RestController
public class ProductController {
@Autowired
ProductService productService;
@ApiOperation("商品详情")
@GetMapping("product/detail")
public ApiRestResponse detail(@RequestParam Integer id){
Product product = productService.detail(id);
return ApiRestResponse.success(product);
}
//73.开发前台商品列表 开一个ProductListReq.java 去ProductServiceImpl
@ApiOperation("商品详情")
@GetMapping("product/list")
public ApiRestResponse list(ProductListReq productListReq){
PageInfo list = productService.list(productListReq);
return ApiRestResponse.success(list);
}
}
总结商品模块
- 重难点:商品的搜索[like]、排序[枚举+自定义]、目录查询[所有目录id都查到 再用查询列表方式]
- 常见错误:更新和新增放在同一个接口、排序字段不用枚举
购物车模块介绍
业务流程
- 添加商品到购物车 → 商品是否在售、是否有库存
- →[否] 提示用户
- →[是] 该商品之前就在购物车里
- →[否] 添加新商品
- →[是] 原有基础上添加数量
com/imooc/mall/controller/CartController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 76.购物车Controller 77.用一个通用UserFilter逻辑
*/
@Controller
@RequestMapping("/cart")
public class CartController {//因为放在url中 要加数据绑定 通过HttpSession获取用户信息太麻烦 用一个通用filter逻辑
@PostMapping("/add")
public ApiRestResponse add(@RequestParam Integer productId, @RequestParam Integer count){
return null;
}
}
========================================================
127.0.0.1:8083/cart/add?productId=22&count=1
com/imooc/mall/filter/UserFilter.java
package com.imooc.mall.filter;
import com.imooc.mall.common.Constant;
import com.imooc.mall.model.pojo.User;
import com.imooc.mall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
/**
*
* 77.用户过滤器 希望把用户信息currentUser保存下来
*/
public class UserFilter implements Filter {
public static User currentUser;
@Autowired
UserService userService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//对身份进行校验 用session获取当前的用户
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpSession session = request.getSession();
currentUser = (User) session.getAttribute(Constant.IMOOC_MALL_USER);
if (currentUser == null) {
PrintWriter out = new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter();
out.write("{\n" //用户未登录
+ " \"status\": 10007,\n"
+ " \"msg\": \"NEED_LOGIN\",\n"
+ " \"data\": null\n"
+ "}");
out.flush();
out.close();
return;
}
//78.续写过滤器链条 写好后对用户链条进行config配置 UserFilterConfig.java
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
com/imooc/mall/config/UserFilterConfig.java
package com.imooc.mall.config;
import com.imooc.mall.filter.UserFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 78.对调用接口时对哪个类进行拦截 过滤器的配置 79创建cartService
*/
@Configuration
public class UserFilterConfig {
@Bean
public UserFilter userFilter(){
return new UserFilter();
}
@Bean(name = "userFilterConf")
public FilterRegistrationBean adminFilterConfig(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(userFilter());
//下面的是校验与拦截
filterRegistrationBean.addUrlPatterns("/cart/*"); //CartController.java
filterRegistrationBean.addUrlPatterns("/order/*");
filterRegistrationBean.setName("userFilterConf");
return filterRegistrationBean;
}
}
com/imooc/mall/service/impl/CartServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.model.dao.CategoryMapper;
import com.imooc.mall.model.dao.ProductMapper;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.vo.CartVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 80.购物车Service实现类 其中List<>中间是需要包括 商品id 图片 名字 商品选中 数量信息
* 81.创建一个VO增加那些属性放在<>中 是返回给前端组合后的对象
*/
@Service
public class CartServiceImpl {
@Autowired
ProductMapper productMapper;
@Autowired
CategoryMapper categoryMapper;
public List<CartVO> add(Integer userId, Integer productId, Integer count) {
validProduct(productId, count);
}
private void validProduct(Integer productId, Integer count) {
Product product = productMapper.selectByPrimaryKey(productId);
//判断商品是否存在,商品是否上架
if (product == null || product.getStatus().equals(Constant.SaleStatus.NOT_SALE)) {
//82. 1是上架 1不明确要形成代码让顾客知道 Constant.java
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_SALE);
}
//判断商品库存 如果要买的比库存多 就买不了
if (count > product.getStock()) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
}
}
}
com/imooc/mall/common/Constant.java
//83.创建商品上下架的状态 创建CartMapper[Cart selectCartByUserIdAndProductId] 写sql
//84再去CartServiceImpl
public interface SaleStatus{
int NOT_SALE = 0; //商品下架状态
int SALE = 1; //商品上架状态
}
@Service
public class CartServiceImpl implements CartService {
@Autowired
ProductMapper productMapper;
@Autowired
CategoryMapper categoryMapper;
@Autowired
CartMapper cartMapper;
@Override
public List<CartVO> add(Integer userId, Integer productId, Integer count) {
validProduct(productId, count);
Cart cart = cartMapper.selectCartByUserIdAndProductId(userId, productId);
//84.补全代码
if (cart==null){
//这个商品之前不在购物车里,需要新增一个记录
cart = new Cart();
cart.setProductId(productId);
cart.setUserId(userId);
cart.setQuantity(count);
cart.setSelected(Constant.Cart.CHECKED);
cartMapper.insertSelective(cart);
}else {
//这个商品已经在购物车里了,数量相加
count = cart.getQuantity() + count;
Cart cartNew = new Cart();
cartNew.setQuantity(count);
cartNew.setId(cart.getId());
cartNew.setProductId(cart.getProductId());
cartNew.setUserId(cart.getUserId());
//无论是否想买都选中
cartNew.setSelected(Constant.Cart.CHECKED);
cartMapper.updateByPrimaryKeySelective(cartNew);
}
//85.去CartController补全代码逻辑 返回购物车列表哦~ 86.CartMapper.java
return null;
}
private void validProduct(Integer productId, Integer count) {
Product product = productMapper.selectByPrimaryKey(productId);
//判断商品是否存在,商品是否上架
if (product == null || product.getStatus().equals(Constant.SaleStatus.NOT_SALE)) {
//82. 1是上架 1不明确要形成代码让顾客知道 Constant.java
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_SALE);
}
//判断商品库存 如果要买的比库存多 就买不了
if (count > product.getStock()) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
}
}
}
com/imooc/mall/model/dao/CartMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Cart;
import com.imooc.mall.model.vo.CartVO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Repository
public interface CartMapper {
int deleteByPrimaryKey(Integer id);
int insert(Cart record);
int insertSelective(Cart record);
Cart selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Cart record);
int updateByPrimaryKey(Cart record);
//86.选中列表的方法 传入的参数是userId
List<CartVO> selectList(@Param("userId") Integer userId);
Cart selectCartByUserIdAndProductId(@Param("userId") Integer userId, @Param("productId")Integer productId);
}
mappers/CartMapper.xml
<select id="selectList" resultType="com.imooc.mall.model.vo.CartVO" parameterType="java.lang.Integer">
select
c.id as id,
p.id as productId,
c.user_id as userId,
c.quantity as quantity,
c.selected as selected,
p.price as price,
p.name as productName,
p.image as productImage
from imooc_mall_cart c
left join imooc_mall_product p on p.id = c.product_id
where c.user_id = #{userId}
and p.status = 1
</select>
com/imooc/mall/controller/CartController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.filter.UserFilter;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.service.CartService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 76.购物车Controller 77.用一个通用User_filter逻辑
*/
@RestController //404的错误① 返回的json格式 要用RestController
@RequestMapping("/cart")
public class CartController {//因为放在url中 要加数据绑定 通过HttpSession获取用户信息太麻烦 用一个通用filter逻辑
@Autowired
CartService cartService;
@PostMapping("/list")
@ApiOperation("购物车列表")
public ApiRestResponse list(){
//86.去写service的list接口 CartMapper中写选中列表的方法
//内部获取用户Id,防止横向越权
List<CartVO> cartList = cartService.list(UserFilter.currentUser.getId());
return ApiRestResponse.success();
}
@PostMapping("/add")
@ApiOperation("添加商品到购物车")
public ApiRestResponse add(@RequestParam Integer productId, @RequestParam Integer count){
//85.补全完逻辑代码 87在下面补全代码 List<CartVO> cartVOList =
List<CartVO> cartVOList = cartService.add(UserFilter.currentUser.getId(), productId, count);
return ApiRestResponse.success(cartVOList);
}
}
更新、删除购物车接口
com/imooc/mall/controller/CartController.java
@PostMapping("/update")
@ApiOperation("更新购物车")
public ApiRestResponse update(@RequestParam Integer productId, @RequestParam Integer count){
//86.去service层中写更新方法 CartServiceImpl.java
List<CartVO> cartVOList = cartService.update(UserFilter.currentUser.getId(), productId, count);
return ApiRestResponse.success(cartVOList);
}
@PostMapping("/delete")
@ApiOperation("删除保护购物车")
public ApiRestResponse delete(@RequestParam Integer productId){
//87.删除保护购物车方法
//不能传入userId, cartId, 否则可以删除别人的购物车
List<CartVO> cartVOList = cartService.delete(UserFilter.currentUser.getId(), productId);
return ApiRestResponse.success(cartVOList);
}
=====================================================================
127.0.0.1:8083/cart/delete?productId=22
com/imooc/mall/service/impl/CartServiceImpl.java
@Override
public List<CartVO> update(Integer userId, Integer productId, Integer count){
//86.去service层中写更新方法
validProduct(productId, count);
Cart cart = cartMapper.selectCartByUserIdAndProductId(userId, productId);
if (cart==null){
//这个商品之前不在购物车里,无法更新
throw new ImoocMallException(ImoocMallExceptionEnum.UPDATE_FAILD);
}else {
//这个商品已经在购物车里了,则更新数量
Cart cartNew = new Cart();
cartNew.setQuantity(count);
cartNew.setId(cart.getId());
cartNew.setProductId(cart.getProductId());
cartNew.setUserId(cart.getUserId());
//无论是否想买都选中
cartNew.setSelected(Constant.Cart.CHECKED);
cartMapper.updateByPrimaryKeySelective(cartNew);
}
return this.list(userId);
}
@Override
public List<CartVO> delete(Integer userId, Integer productId){
//86.去service层中写更新方法
Cart cart = cartMapper.selectCartByUserIdAndProductId(userId, productId);
if (cart==null){
//这个商品之前不在购物车里,无法更新
throw new ImoocMallException(ImoocMallExceptionEnum.DELETE_FAILED);
}else {
//这个商品已经在购物车里了,则可以删除
cartMapper.deleteByPrimaryKey(cart.getId());
}
return this.list(userId);
}
======================================================================
127.0.0.1:8083/cart/update?productId=22&count=1
com/imooc/mall/service/CartService.java
package com.imooc.mall.service;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.model.pojo.Category;
import com.imooc.mall.model.request.AddCategoryReq;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.model.vo.CategoryVO;
import java.util.List;
/**
* 79.购物车service 增加CartServiceImpl
*/
public interface CartService {
//87.补全list实现类
List<CartVO> list(Integer userId);
List<CartVO> add(Integer userId, Integer productId, Integer count);
List<CartVO> update(Integer userId, Integer productId, Integer count);
List<CartVO> delete(Integer userId, Integer productId);
}
浅谈@RequestParam、@RequestBody、@PathVariable - 知乎 (zhihu.com)
选中购物车相关接口
com/imooc/mall/controller/CartController.java
@PostMapping("/select")
@ApiOperation("选择/不选择购物车的某商品")
public ApiRestResponse select(@RequestParam Integer productId, @RequestParam Integer selected){
//88.选/不选购物车某商品
//不能传入userId, cartId, 否则可以删除别人的购物车
List<CartVO> cartVOList = cartService.selectOrNot(UserFilter.currentUser.getId(), productId,selected);
return ApiRestResponse.success(cartVOList);
}
@PostMapping("/selectAll")
@ApiOperation("全选择/全不选择购物车的某商品")
public ApiRestResponse selectAll(@RequestParam Integer selected){
//89.全选/全不选购物车某商品
//不能传入userId, cartId, 否则可以删除别人的购物车
////90.订单模块接口编写 91创建OrderController.java OrderService.java OrderServiceImpl.java
List<CartVO> cartVOList = cartService.selectAllOrNot(UserFilter.currentUser.getId(), selected);
return ApiRestResponse.success(cartVOList);
}
com/imooc/mall/service/impl/CartServiceImpl.java
@Override
public List<CartVO> selectOrNot(Integer userId, Integer productId, Integer selected){
Cart cart = cartMapper.selectCartByUserIdAndProductId(userId, productId);
if (cart==null){
//88.这个商品之前不在购物车里,无法选择/不选中
throw new ImoocMallException(ImoocMallExceptionEnum.UPDATE_FAILD);
}else {
//这个商品已经在购物车里了,则可以选中/不选中
cartMapper.selectOrNot(userId, productId, selected);
}//返回购物车列表
return this.list(userId);
}
@Override
public List<CartVO> selectAllOrNot(Integer userId, Integer selected){
//89.这里填null是对mapper中的sql进行等于null判断的语句 改变选中状态
cartMapper.selectOrNot(userId, null, selected);
return this.list(userId);
}
com/imooc/mall/service/CartService.java
package com.imooc.mall.service;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.model.pojo.Category;
import com.imooc.mall.model.request.AddCategoryReq;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.model.vo.CategoryVO;
import java.util.List;
/**
* 79.购物车service 增加CartServiceImpl
*/
public interface CartService {
//87.补全list实现类
List<CartVO> list(Integer userId);
List<CartVO> add(Integer userId, Integer productId, Integer count);
List<CartVO> update(Integer userId, Integer productId, Integer count);
List<CartVO> delete(Integer userId, Integer productId);
List<CartVO> selectOrNot(Integer userId, Integer productId, Integer selected);
List<CartVO> selectAllOrNot(Integer userId, Integer selected);
}
总结购物车模块
重难点
- MyBatis返回非标准对象、后期计算单样商品的总价 [数据库无法直接查到]
- 添加商品到购物车时,根据是否已经存在该商品,有不同逻辑
常见错误:不做越权判断 [不允许前端传入Id]
创建订单接口 - 主流程框架搭建
- 登录 → 浏览商品 → 加入购物车 → 下单
- → 取消订单
- → 扫码支付 → 发货 → 收获 → 订单完结
生成订单 —— 用户下单
- 入参
- 从购物车中查找已经勾选的商品
- 判断商品是否正在售卖中
- 判断库存,保证不超卖,扣库存
- 数据库事务(实操演示效果)
- 删除购物车中对应的商品
- 生成订单
- 订单号生成规则
- 循环保存每个商品到order_item表
- 进入Coding阶段
com/imooc/mall/controller/OrderController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 91.订单Controller 新建一个根据pojo的Order → CreateOrderReq 新建一个OrderService
*/
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("order/create")
public ApiRestResponse create(@RequestBody CreateOrderReq createOrderReq){
return null;
}
}
com/imooc/mall/service/impl/OrderServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.filter.UserFilter;
import com.imooc.mall.model.dao.CartMapper;
import com.imooc.mall.model.dao.CategoryMapper;
import com.imooc.mall.model.dao.ProductMapper;
import com.imooc.mall.model.pojo.Cart;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.service.CartService;
import com.imooc.mall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 91.★★ 订单Service实现类 ★★
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
CartService cartService;
public String create(CreateOrderReq createOrderReq) {
//拿到用户ID
Integer userId = UserFilter.currentUser.getId();
//从购物车查找已经勾选的商品 CartVO里面包含着任何信息
List<CartVO> cartVOList = cartService.list(userId);
//勾选的单独拿出来
ArrayList<CartVO> cartVOListTemp = new ArrayList<>();
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
if (cartVO.getSelected().equals(Constant.Cart.CHECKED)) {
cartVOListTemp.add(cartVO);
}
}
cartVOList = cartVOListTemp;
//如果购物车已勾选的为空,报错
if (CollectionUtils.isEmpty(cartVOList)){
throw new ImoocMallException(ImoocMallExceptionEnum.CART_EMPTY);
}
}
//判断商品是否存在、上下架状态、库存
//把购物车对象转换为订单item对象
//扣库存
//把购物车中的已勾选商品删除
//生成订单
//生成订单号,有独立的规则
//循环保存每个商品的order_item表
//把结果返回
}
}
com/imooc/mall/exception/ImoocMallExceptionEnum.java
package com.imooc.mall.exception;
/**
* 异常枚举
*/
//13.编写异常枚举 注意类是enum噢 14返回ApiRestResponse
public enum ImoocMallExceptionEnum {
// 正确状态码是10000 这个错误的就10001 变红是因为没构造函数
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD_NAME(10002,"密码不能为空"),
PASSWORD_TOO_SHORT(10003,"密码长度不能小于8位"),
NAME_EXISTED(10004,"不允许重名"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
NEED_LOGIN(10007,"用户未登录"),
UPDATE_FAILD(10008,"更新失败"),
NEED_ADMIN(10009,"无管理员权限"),
NAME_NOT_NULL(10010,"参数不能为空"),
CREATE_FAILED(10011,"新增失败"),
REQUEST_PARAM_ERROR(10012,"参数错误"),
DELETE_FAILED(10013,"删除失败"),
MKDIR_FAILED(10014,"文件夹创建失败"),
UPLOAD_FAILED(10015,"图片上传失败"),
NOT_SALE(10016,"商品状态不可售"),
NOT_ENOUGH(10017,"商品库存不足"),
CART_EMPTY(10018, "购物车已勾选的商品为空"),
SYSTEM_ERROR(20000,"系统异常");
//异常码
Integer code;
//异常信息
String msg;
ImoocMallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
创建订单 —— 对象转换与扣库存
com/imooc/mall/controller/OrderController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 91.订单Controller 新建一个根据pojo的Order → CreateOrderReq 新建一个OrderService
*/
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("order/create")
@ApiOperation("创建订单")
public ApiRestResponse create(@RequestBody CreateOrderReq createOrderReq){
//95.调用orderService
String orderNo = orderService.create(createOrderReq);
return ApiRestResponse.success(orderNo);
}
}
com/imooc/mall/service/impl/OrderServiceImpl.java
package com.imooc.mall.service.impl;
import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.filter.UserFilter;
import com.imooc.mall.model.dao.*;
import com.imooc.mall.model.pojo.Order;
import com.imooc.mall.model.pojo.OrderItem;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.service.CartService;
import com.imooc.mall.service.OrderService;
import com.imooc.mall.util.OrderCodeFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 91.★★ 订单Service实现类 ★★
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
CartService cartService;
@Autowired
ProductMapper productMapper;
@Autowired
CartMapper cartMapper;
@Autowired
OrderMapper orderMapper;
@Autowired
OrderItemMapper orderItemMapper;
@Override
public String create(CreateOrderReq createOrderReq) {
//拿到用户ID
Integer userId = UserFilter.currentUser.getId();
//从购物车查找已经勾选的商品 CartVO里面包含着任何信息
List<CartVO> cartVOList = cartService.list(userId);
//勾选的单独拿出来
ArrayList<CartVO> cartVOListTemp = new ArrayList<>();
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
if (cartVO.getSelected().equals(Constant.Cart.CHECKED)) {
cartVOListTemp.add(cartVO);
}
}
cartVOList = cartVOListTemp;
//如果购物车已勾选的为空,报错
if (CollectionUtils.isEmpty(cartVOList)){
throw new ImoocMallException(ImoocMallExceptionEnum.CART_EMPTY);
}
//判断商品是否存在、上下架状态、库存
validSaleStatusAndStock(cartVOList);
//把购物车对象转换为订单item对象
List<OrderItem> orderItemList = cartVOListToOrderItemList(cartVOList);
//扣库存
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
Product product = productMapper.selectByPrimaryKey(orderItem.getProductId());
int stock = product.getStock() - orderItem.getQuantity();
if (stock < 0){
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
}
product.setStock(stock);
productMapper.updateByPrimaryKeySelective(product);
}
//把购物车中的已勾选商品删除
cleanCart(cartVOList);
//生成订单
Order order = new Order();
//93.创建一个Util/OrderCodeFactory
//生成订单号,有独立的规则
String orderNo = OrderCodeFactory.getOrderCode(Long.valueOf(userId));
order.setOrderNo(orderNo);
order.setUserId(userId);
order.setTotalPrice(totalPrice(orderItemList));
order.setReceiverName(createOrderReq.getReceiverName());
order.setReceiverMobile(createOrderReq.getReceiverMobile());
order.setReceiverAddress(createOrderReq.getReceiverAddress());
order.setOrderStatus(Constant.OrderStatusEnum.NOT_PAID.getCode()); //94.去Constant定义订单状态
//循环保存每个商品的order_item表
order.setPostage(0);
order.setPaymentType(1);
//插入到Order表
orderMapper.insertSelective(order);
//循环保存每个商品到order_item表
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
orderItem.setOrderNo(order.getOrderNo());
orderItemMapper.insertSelective(orderItem);
}
//把结果返回
return orderNo;
//95.回到Controller调用
}
//92.生成相似代码并改造
private void validSaleStatusAndStock(List<CartVO> cartVOList) {
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
Product product = productMapper.selectByPrimaryKey(cartVO.getProductId());
//判断商品是否存在,商品是否上架
if (product == null || product.getStatus().equals(Constant.SaleStatus.NOT_SALE)) {
//82. 1是上架 1不明确要形成代码让顾客知道 Constant.java
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_SALE);
}
//判断商品库存 如果要买的比库存多 就买不了
if (cartVO.getQuantity() > product.getStock()) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
}
}
}
private List<OrderItem> cartVOListToOrderItemList(List<CartVO> cartVOList) {
List<OrderItem> orderItemList = new ArrayList<>();
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
OrderItem orderItem = new OrderItem();
orderItem.setProductId(cartVO.getProductId());
//记录商品快照信息
orderItem.setProductName(cartVO.getProductName());
orderItem.setProductImg(cartVO.getProductImage());
orderItem.setUnitPrice(cartVO.getPrice());
orderItem.setQuantity(cartVO.getQuantity());
orderItem.setTotalPrice(cartVO.getTotalPrice());
orderItemList.add(orderItem);
}
return orderItemList;
}
private void cleanCart(List<CartVO> cartVOList){
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
cartMapper.deleteByPrimaryKey(cartVO.getId());
}
}
private Integer totalPrice(List<OrderItem> orderItemList){
Integer totalPrice = 0;
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;
}
}
com/imooc/mall/util/OrderCodeFactory.java
package com.imooc.mall.util;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
/**
* 描述: 生成订单No工具类
*/
public class OrderCodeFactory {
/**
* 订单类别头
*/
private static final String ORDER_CODE = "1";
/**
* 随机编码
*/
private static final int[] r = new int[]{7, 9, 6, 2, 8, 1, 3, 0, 5, 4};
/**
* 用户id和随机数总长度
*/
private static final int maxLength = 5;
/**
* 更具id进行加密+加随机数组成固定长度编码
*/
private static String toCode(Long id) {
String idStr = id.toString();
StringBuilder idSb = new StringBuilder();
for (int i = idStr.length() - 1; i >= 0; i--) {
idSb.append(r[idStr.charAt(i) - '0']);
}
return idSb.append(getRandom(maxLength - idStr.length())).toString();
}
/**
* 生成时间戳
*/
private static String getDateTime() {
DateFormat sdf = new SimpleDateFormat("HHmmss");
return sdf.format(new Date());
}
/**
* 生成固定长度随机码
*
* @param n 长度
*/
private static long getRandom(long n) {
long min = 1, max = 9;
for (int i = 1; i < n; i++) {
min *= 10;
max *= 10;
}
long rangeLong = (((long) (new Random().nextDouble() * (max - min)))) + min;
return rangeLong;
}
/**
* 生成不带类别标头的编码
*/
private static synchronized String getCode(Long userId) {
userId = userId == null ? 10000 : userId;
return getDateTime() + toCode(userId);
}
/**
* 生成订单单号编码
*/
public static String getOrderCode(Long userId) {
return ORDER_CODE + getCode(userId);
}
}
com/imooc/mall/common/Constant.java
package com.imooc.mall.common;
import com.google.common.collect.Sets;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 21.SALT常量值 写完以后去UserServiceImpl重写密码代码
*/
@Component //让spring帮注入value
public class Constant {
public static final String SALT = "aSp[PCx,aw.xq246}";
public static final String IMOOC_MALL_USER = "imooc_mall_user";
public static String FILE_UPLOAD_DIR;
//64.为了解决上传图片系统异常报错 注入失败的原因是上方是static普通变量 set方法把静态变量赋值
@Value("${file.upload.dir}")
public void setFileUploadDir(String fileUploadDir){
FILE_UPLOAD_DIR = fileUploadDir;
}//65.打开ImoocMallWebMvcConfig 加一个映射规则
//74.排序处理 去Constant定义支持的排序模式和手段
public interface ProductListOrderBy{
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price desc","price asc");
}
//83.创建商品上下架的状态 创建CartMapper[Cart selectCartByUserIdAndProductId] 写sql 84再去CartServiceImpl
public interface SaleStatus{
int NOT_SALE = 0; //商品下架状态
int SALE = 1; //商品上架状态
}
public interface Cart{
int UN_CHECKED = 0; //购物车未选中状态
int CHECKED = 1; //购物车选中状态
}
public enum OrderStatusEnum{
CANCELED(0, "用户已取消"),
NOT_PAID(10, "未付款"),
PAID(20, "已付款"),
DELIVERED(30, "已发货"),
;
private String value;
private int code;
//94.去Constant定义订单状态
OrderStatusEnum(int code,String value){
this.value = value;
this.code = code;
}
public static OrderStatusEnum codeOf(int code){
for (OrderStatusEnum orderStatusEnum : values()){
if (orderStatusEnum.getCode() == code){
return orderStatusEnum;
}
}
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ENUM);
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
}
添加数据库事务
//95.写数据库事务代码 @Transactional (要么全对全要 要么全不要)
@Transactional(rollbackFor = Exception.class)
@Override
public String create(CreateOrderReq createOrderReq) {
......
}
订单详情
com/imooc/mall/service/impl/OrderServiceImpl.java
/* 95.继续实现Service实现类 拷贝一个Order成OrderVo 新增两个属性
private String orderStatusName;
private List<OrderItemVo> orderItemVoList;
因为没有<OrderItemVo>要根据需求去创建
拷贝OrderItem成OrderItemVO 更改属性
之后回来补写代码
*/
public OrderVO detail(String orderNo){
}
com/imooc/mall/model/vo/OrderItemVO.java
public class OrderItemVO {
private Integer id;
private String orderNo;
private Integer productId;
private String productName;
private String productImg;
private Integer unitPrice;
private Integer quantity;
private Integer totalPrice;
com/imooc/mall/model/vo/OrderVO.java
public class OrderVO {
private Integer id;
private String orderNo;
private Integer userId;
private Integer totalPrice;
private String receiverName;
private String receiverMobile;
private String receiverAddress;
private Integer orderStatus;
private Integer postage;
private Integer paymentType;
private Date deliveryTime;
private Date payTime;
private Date endTime;
private Date createTime;
private Date updateTime;
订单状态
- 0 用户已取消
- 10 未付款(下单后的初始状态)
- 20 已付款
- 30 已发货
- 40 交易完成
com/imooc/mall/service/impl/OrderServiceImpl.java
/* 95.继续实现Service实现类 拷贝一个Order成OrderVo 新增两个属性
private String orderStatusName;
private List<OrderItemVo> orderItemVoList;
因为没有<OrderItemVo>要根据需求去创建
拷贝OrderItem成OrderItemVO 更改属性
之后回来补写代码
*/
@Override
public OrderVO detail(String orderNo){
//96.去创建mapper的方法后回来补写代码 97写订单controller
Order order = orderMapper.selectByOrderNo(orderNo);
//订单不存在,则报错
if (order == null){
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
}
//订单存在,需要判断所属
Integer userId = UserFilter.currentUser.getId();
if (!order.getUserId().equals(userId)) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_YOUR_ORDER);
}
OrderVO orderVO = getOrderVo(order);
return orderVO;
}
private OrderVO getOrderVo(Order order) {
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(order,orderVO);
//获取订单对应的orderItemVOList
List<OrderItem> orderItemList = orderItemMapper.selectByOrderNo(order.getOrderNo());
//OrderItem和OrderItem相比少了几个字段
List<OrderItemVO> orderItemVOList = new ArrayList<>();
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
OrderItemVO orderItemVO = new OrderItemVO();
BeanUtils.copyProperties(orderItem, orderItemVO);
orderItemVOList.add(orderItemVO);
}
orderVO.setOrderItemVOList(orderItemVOList);
//com/imooc/mall/common/Constant.java的列举枚举
orderVO.setOrderStatusName(Constant.OrderStatusEnum.codeOf(orderVO.getOrderStatus()).getValue());
return orderVO;
}
com/imooc/mall/model/dao/OrderMapper.java
package com.imooc.mall.model.dao;
import com.imooc.mall.model.pojo.Order;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderMapper {
int deleteByPrimaryKey(Integer id);
int insert(Order record);
int insertSelective(Order record);
Order selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Order record);
int updateByPrimaryKey(Order record);
Order selectByOrderNo(String orderNo);
}
=====================================================
<select id="selectByOrderNo" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List"/>
from imooc_mall_order
where order_no = #{orderNo}
</select>
com/imooc/mall/controller/OrderController.java
package com.imooc.mall.controller;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.model.vo.OrderVO;
import com.imooc.mall.service.OrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 91.订单Controller 新建一个根据pojo的Order → CreateOrderReq 新建一个OrderService
*/
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping("order/create")
@ApiOperation("创建订单")
public ApiRestResponse create(@RequestBody CreateOrderReq createOrderReq){
//95.调用orderService
String orderNo = orderService.create(createOrderReq);
return ApiRestResponse.success(orderNo);
}
@GetMapping("order/detail")
@ApiOperation("前台订单详情")
public ApiRestResponse detail(@RequestParam String orderNo){
//95.调用orderService Service层具体实现
OrderVO orderVO = orderService.detail(orderNo);
return ApiRestResponse.success(orderVO);
}
}
==========================================
生成订单详情 GET:127.0.0.1:8083/order/detail?orderNo=100233359639
订单列表
com/imooc/mall/controller/OrderController.java
//97.写一个订单列表controller
@GetMapping("order/list")
@ApiOperation("前台订单列表")
public ApiRestResponse list(@RequestParam Integer pageNum, @RequestParam Integer pageSize){
//95.调用list Service Service层具体实现
PageInfo pageInfo = orderService.listForCustomer(pageNum, pageSize);
return ApiRestResponse.success(pageInfo);
}
===============================================
订单列表:GET:127.0.0.1:8083/order/list?pageNum=1&pageSize=10
com/imooc/mall/service/impl/OrderServiceImpl.java
//97.前台订单实现类 搞个OrderMapper中的selectForCustomer
@Override
public PageInfo listForCustomer(Integer pageNum, Integer pageSize){
Integer userId = UserFilter.currentUser.getId();
PageHelper.startPage(pageNum, pageSize);
List<Order> orderList = orderMapper.selectForCustomer(userId);
List<OrderVO> orderVOList = orderListToOrderVOList(orderList);
//新建一个pageinfo返回 方便前端查看
PageInfo pageInfo = new PageInfo<>(orderList);
pageInfo.setList(orderVOList);
return pageInfo;
}
private List<OrderVO> orderListToOrderVOList(List<Order> orderList) {
List<OrderVO> orderVOList = new ArrayList<>();
for (int i = 0; i < orderList.size(); i++) {
Order order = orderList.get(i);
OrderVO orderVO = getOrderVo(order);
orderVOList.add(orderVO);
}
return orderVOList;
}
com/imooc/mall/model/dao/OrderMapper.java
List<Order> selectForCustomer(Integer userId);
===============================================
mappers/OrderMapper.xml
<select id="selectForCustomer" resultMap="BaseResultMap" parameterType="integer">
select
<include refid="Base_Column_List"/>
from imooc_mall_order
where user_id = #{userId}
order by create_time desc
</select>
取消订单
com/imooc/mall/controller/OrderController.java
//98.写一个订单取消controller
@PostMapping("order/cancel")
@ApiOperation("前台取消订单")
public ApiRestResponse cancel(@RequestParam String orderNo){
orderService.cancel(orderNo);
return ApiRestResponse.success();
}
com/imooc/mall/service/impl/OrderServiceImpl.java
//98.写一个订单取消实现类
@Override
public void cancel(String orderNo){
//能查到订单就取消
Order order = orderMapper.selectByOrderNo(orderNo);
//查不到订单,报错
if (order == null){
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
}
//验证用户身份
//订单存在,需要判断所属
Integer userId = UserFilter.currentUser.getId();
if (!order.getUserId().equals(userId)) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_YOUR_ORDER);
}
//没有付款才可以取消
if (order.getOrderStatus().equals(Constant.OrderStatusEnum.NOT_PAID.getCode())) {
order.setOrderStatus(Constant.OrderStatusEnum.CANCELED.getCode());
order.setEndTime(new Date());
//更新状态
orderMapper.updateByPrimaryKeySelective(order);
}else {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
}
}
二维码接口开发
在线生成 QR Code (oschina.net)
Springboot 上传图片到项目路径下不能访问,需要重启_springboot能上传图片到win11中但无法访问图片-CSDN博客
com/imooc/mall/controller/OrderController.java
//99.生成支付二维码
@GetMapping("order/qrcode")
@ApiOperation("前台取消订单")
public ApiRestResponse qrcode(@RequestParam String orderNo){
orderService.cancel(orderNo);
return ApiRestResponse.success();
}
com/imooc/mall/service/impl/OrderServiceImpl.java
//99.生成支付二维码 pom.xml生成新的二维码依赖 util新建一个QRCodeGenerator类
@Override
public String qrcode(String orderNo) {
//得到请求相关的信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();//不能直接拿到内网的信息 要经过多层转发才可以
//去application.properties配置一下可以访问的ip 去上面String ip
String address = ip + ":" + request.getLocalPort();//拼接一下 这下生成的是整个地址
String payUrl = "http://" + address + "/pay?orderNo=" + orderNo;
System.out.println(address);
System.out.println(payUrl);
try {
QRCodeGenerator.generateQRCodeImage(payUrl, 350, 350, Constant.FILE_UPLOAD_DIR + orderNo + ".png");
} catch (WriterException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
//这个图片可以通过这个url访问到
String pngAddress = "http://" + address + "/images/" + orderNo + ".png";
return pngAddress;
}
com/imooc/mall/util/QRCodeGenerator.java
package com.imooc.mall.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
/**
* 99.生成二维码工具
*/
public class QRCodeGenerator {
public static void generateQRCodeImage(String text, int width, int height, String filePath) throws WriterException, IOException {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);
Path path = FileSystems.getDefault().getPath(filePath);
MatrixToImageWriter.writeToPath(bitMatrix,"PNG",path);
}
public static void main(String[] args) {
try {
generateQRCodeImage("PANCHUNYAO", 350,350,"/Users/Pluminary/Desktop/idea_Space/mall_file_upload/QRTest.png");
} catch (WriterException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
application.properties
file.upload.dir=/Users/Pluminary/Desktop/idea_Space/mall_file_upload/
file.upload.ip=127.0.0.1
com/imooc/mall/service/impl/OrderServiceImpl.java
/**
* 91.★★ 订单Service实现类 ★★
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
CartService cartService;
@Autowired
ProductMapper productMapper;
@Autowired
CartMapper cartMapper;
@Autowired
OrderMapper orderMapper;
@Autowired
OrderItemMapper orderItemMapper;
@Value("${file.upload.ip}")
String ip;
............
}
后台订单列表接口
com/imooc/mall/controller/OrderAdminController.java
package com.imooc.mall.controller;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.service.OrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 100.订单后台管理Controller
*/
@RestController
public class OrderAdminController {
@Autowired
OrderService orderService;
@GetMapping("admin/order/list")
@ApiOperation("管理员订单列表")
public ApiRestResponse listForAdmin(@RequestParam Integer pageNum, @RequestParam Integer pageSize){
PageInfo pageInfo = orderService.listForAdmin(pageNum, pageSize);
return ApiRestResponse.success(pageInfo);
}
}
com/imooc/mall/service/impl/OrderServiceImpl.java
//100.实现类
@Override
public PageInfo listForAdmin(Integer pageNum, Integer pageSize) {//想看到所有订单
PageHelper.startPage(pageNum, pageSize);
List<Order> orderList = orderMapper.selectAllForAdmin();
List<OrderVO> orderVOList = orderListToOrderVOList(orderList);
//新建一个pageinfo返回 方便前端查看
PageInfo pageInfo = new PageInfo<>(orderList);
pageInfo.setList(orderVOList);
return pageInfo;
}
com/imooc/mall/model/dao/OrderMapper.java
List<Order> selectAllForAdmin();
===========================================
mappers/OrderMapper.xml
<select id="selectAllForAdmin" resultMap="BaseResultMap" parameterType="integer">
select
<include refid="Base_Column_List"/>
from imooc_mall_order
order by create_time desc
</select>
支付
根据订单号生成支付二维码 → 扫码支付 → 访问支付URL, 完成支付
com/imooc/mall/controller/OrderController.java
//101.支付接口
@GetMapping("pay")
@ApiOperation("支付接口")
public ApiRestResponse pay(@RequestParam String orderNo){
orderService.pay(orderNo);
return ApiRestResponse.success();
}
com/imooc/mall/service/impl/OrderServiceImpl.java
//101.支付
@Override
public void pay(String orderNo){
//能查到订单就取消
Order order = orderMapper.selectByOrderNo(orderNo);
//查不到订单,报错
if (order == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
}
//支付前的判断 未付款才可以付款
if (order.getOrderStatus() == Constant.OrderStatusEnum.NOT_PAID.getCode()) {
order.setOrderStatus(Constant.OrderStatusEnum.PAID.getCode());
order.setPayTime(new Date());
orderMapper.updateByPrimaryKeySelective(order);
}else {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
}
}
完结订单
com/imooc/mall/controller/OrderAdminController.java
package com.imooc.mall.controller;
import com.github.pagehelper.PageInfo;
import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.service.OrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 100.订单后台管理Controller
*/
@RestController
public class OrderAdminController {
@Autowired
OrderService orderService;
@GetMapping("admin/order/list")
@ApiOperation("管理员订单列表")
public ApiRestResponse listForAdmin(@RequestParam Integer pageNum, @RequestParam Integer pageSize){
PageInfo pageInfo = orderService.listForAdmin(pageNum, pageSize);
return ApiRestResponse.success(pageInfo);
}
/**
* 102.发货。订单状态流程:0用户已取消,10未付款,20已付款,30已发货,40交易完成
*/
@GetMapping("admin/order/delivered")
@ApiOperation("管理员发货")
public ApiRestResponse delivered(@RequestParam String orderNo){
orderService.deliver(orderNo);
return ApiRestResponse.success();
}
/**
* 103.完结订单。订单状态流程:0用户已取消,10未付款,20已付款,30已发货,40交易完成
* 管理员和用户都可以调用
*/
@GetMapping("order/finish")
@ApiOperation("完结订单")
public ApiRestResponse finish(@RequestParam String orderNo){
orderService.finish(orderNo);
return ApiRestResponse.success();
}
}
com/imooc/mall/service/impl/OrderServiceImpl.java
//102.管理订单实现类开发 发货
@Override
public void deliver(String orderNo){
//能查到订单就取消
Order order = orderMapper.selectByOrderNo(orderNo);
//查不到订单,报错
if (order == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
}
//支付前的判断 判断已付款的
if (order.getOrderStatus() == Constant.OrderStatusEnum.PAID.getCode()) {
order.setOrderStatus(Constant.OrderStatusEnum.DELIVERED.getCode());
order.setDeliveryTime(new Date());
orderMapper.updateByPrimaryKeySelective(order);
}else {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
}
}
//103.完结订单
@Override
public void finish(String orderNo){
//能查到订单就取消
Order order = orderMapper.selectByOrderNo(orderNo);
//查不到订单,报错
if (order == null) {
throw new ImoocMallException(ImoocMallExceptionEnum.NO_ORDER);
}
//有可能管理员调用 有可能用户调用 如果是普通用户, 要去校验一下订单所属
if (!userService.checkAdminRole(UserFilter.currentUser) && !order.getUserId().equals(UserFilter.currentUser.getId())) {
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_YOUR_ORDER);
}
//支付前的判断 判断已付款的 //发货后可以完结订单
if (order.getOrderStatus() == Constant.OrderStatusEnum.DELIVERED.getCode()) {
order.setOrderStatus(Constant.OrderStatusEnum.FINISHED.getCode());
order.setEndTime(new Date());
orderMapper.updateByPrimaryKeySelective(order);
}else {
throw new ImoocMallException(ImoocMallExceptionEnum.WRONG_ORDER_STATUS);
}
}
全流程测试
- Postman实操
- 登录 → 浏览商品 → 加入购物车 → 下单
- → 取消订单
- → 扫码支付 → 发货 → 收获 → 订单完结
总结订单模块
重难点
- VO的封装、裁剪
- 一个订单内包括多个商品
- 订单状态流转
- 二维码生成
常见错误:把POJO返回到前端
上线前准备工作
阿里云部署
把每个 com/imooc/mall/model/request/… 类都加上**toString()**方法 方便调试
后台、前台、获取类的行为 的目录列表都是 GetMapping
前端准备工作
把static里的前端文件导入项目中
com/imooc/mall/config/ImoocMallWebMvcConfig.java
//104.ImoocMallWebMvcConfig.java 根据路由配置相关文件 以admin开头的文件会被路由到下面的Locations
package com.imooc.mall.config;
import com.imooc.mall.common.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 42.配置地址映射 43去CategoryController.java 加一个 @ApiOperation("后台添加目录")
* 44.新增一个目录的updateCategory的参数 [UpdateCategoryReq.java ]
*/
@Configuration //代表是一个配置
public class ImoocMallWebMvcConfig implements WebMvcConfigurer {
public void addResourceHandles(ResourceHandlerRegistry registry){
//104.根据路由配置相关文件 以admin开头的文件会被路由到下面的Locations
registry.addResourceHandler("/admin/**").addResourceLocations("classpath:/static/admin");
//65.增加一个registry 66.新增接口继续开发 ProductAdminController
registry.addResourceHandler("/images/**").addResourceLocations("file:" + Constant.FILE_UPLOAD_DIR);
// 把地址给到对应的目录下
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars");
}
}
部署云服务器
- 阿里云简介
- 选择云服务器并购买
- 环境配置
- Spring Boot部署[包括maven打包 sql文件也要上传]
云服务器管理控制台 (aliyun.com)
免费试用三个月
账号: root
密码: panchunyao123!
复制服务器的公网ip:47.98.225.105
cmd本地电脑 → ssh root@47.98.225.105
在阿里云购买Linux服务器,配置宝塔环境,全图文,最最详细图解,保姆级教学_阿里云服务器怎么直接买宝塔-CSDN博客
【宝塔安装jdk1.8(yum安装)】
1、检索检索1.8的列表
yum list java-1.8*
2、安装1.8.0的所有文件
yum install java-1.8.0-openjdk* -y
3、使用命令检查是否安装成功
java -version
登录宝塔 → 绑定宝塔 → 下载软件
宝塔服务器[http://47.98.225.105:22003/]
☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ 宝塔BaoTa ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
========================面板账户登录信息=========================
外网面板地址: http://47.98.225.105:22003/1259ebfa
内网面板地址: http://172.24.91.49:22003/1259ebfa
username: grltvl7d
password: 46303f93
======================== mysql-5.7 ===========================
root密码成功修改为: Panchunyao123![mysql-8.0]
[root@iZbp1dssknxftmjczbtpndZ ~]# mysql -uroot -pPanchunyao123!
mysql> create database imooc_mall;
mysql> use imooc_mall;
Database changed
导入sql文件:imooc_mall_online.sql
从本机将其上传到服务器在cmd中运行:
scp /Users/Pluminary/Desktop/imooc_mall_online.sql root@47.98.225.105:22003:/root
输入服务器密码:panchunyao123!
回到服务器端输入:
mysql> use imooc_mall
Database changed
mysql> source /root/imooc_mall_online.sql
mysql> show tables;
+-----------------------+
| Tables_in_imooc_mall |
+-----------------------+
| imooc_mall_cart |
| imooc_mall_category |
| imooc_mall_order |
| imooc_mall_order_item |
| imooc_mall_product |
| imooc_mall_user |
+-----------------------+
#为数据库赋予权限!!!
mysql> grant all privileges on imooc_mall.* to 'root'@'127.0.0.1' identified by 'Panchunyao123!';
--------------------------------------------------------------
MySql8.0
CREATE USER 'root'@'47.98.225.105' IDENTIFIED BY 'Panchunyao123!';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'47.98.225.105' WITH GRANT OPTION;
--------------------------------------------------------------
mysql> quit
Bye
[root@iZbp1dssknxftmjczbtpndZ ~]# mysql -uroot -pPanchunyao123! -h127.0.0.1
去云服务器 安全组 访问规则 入方向 手动添加一个 目的:8081/8081 源:0.0.0.0/0
==============================================================
整理文件上传到服务器 先**Build → Rebuild Project **
打包 Maven → Lifecycle → clean[之前的内容删除 不容易出错] → package[打包操作]
spring打包报错:Java Runtime (class file version 55.0), class file versions up to 52.0_there was an error in the forked process org/testn-CSDN博客
[INFO] Building jar: C:\Users\Pluminary\Desktop\idea_Space\mall\target\mall-0.0.1-SNAPSHOT.jar
在本机cmd中运行把导好的包发送給服务器
C:\Users\Pluminary>scp C:\Users\Pluminary\Desktop\idea_Space\mall\target\mall-0.0.1-SNAPSHOT.jar root@c:/root
#rm -rf images/ 删除images文件夹
新建一个文件夹
[root@iZbp1dssknxftmjczbtpndZ ~]# pwd
[root@iZbp1dssknxftmjczbtpndZ ~]# mkdir image
图片上传到服务器中[图片尽量在没有根目录空格的文件夹下 比如Spring Boot 中间有空格]
C:\Users\Pluminary>scp C:/Users/Pluminary/Desktop/images/. root@47.98.225.105:/root/images/
在Xshell7中阿里云服务器
[root@iZbp1dssknxftmjczbtpndZ ~]# ls
images imooc_mall_online.sql install.sh mall-0.0.1-SNAPSHOT.jar
每一次部署之前要把当前的java停止掉
[root@iZbp1dssknxftmjczbtpndZ images]# lsof -i:8081
[root@iZbp1dssknxftmjczbtpndZ images]# kill -9 12345 //按照PID杀死端口号
-bash: kill: (12345) - No such process
部署到云服务器 [/root/null 2>&1 &]将日志输出到哪里 这里是丢弃的意思
[root@iZbp1dssknxftmjczbtpndZ ~]# nohup java -jar -Dserver.port=8081 -Dspring.profiles.active=prod /root/mall-0.0.1-SNAPSHOT.jar > /root/null 2>&1 &
[1] 18656
如果没有别名就一个本来的application.properties
[root@iZbp1dssknxftmjczbtpndZ ~]# nohup java -jar -Dserver.port=8081 /root/mall-0.0.1-SNAPSHOT.jar > /root/null 2>&1 &
#代表当前程序的进程号
[root@iZbp1dssknxftmjczbtpndZ ~]# lsof -i:8081 //查询端口号
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 18656 root 19u IPv6 195648 0t0 TCP *:tproxy (LISTEN)
============================================================================
若打不开请检查网页报错 寻找传入的application.prod.properties的
spring.datasource.url地址和spring.datasource.username && password数据库用户和密码
查看服务器的端口: mysql> show global variables like 'port';
阿里云控制台:
https://ecs.console.aliyun.com/server/i-bp1dssknxftmjczbtpnd/detail?regionId=cn-hangzhou
如何查看linux服务器安装了tomcat:
rpm -qa|grep tomcat
宝塔安装目录: $cd /www/server/.....
[root@iZbp1dssknxftmjczbtpndZ data]# cd /www/server/tomcat/bin
#找到文件后启动tomcat
[root@iZbp1dssknxftmjczbtpndZ bin]# ./startup.sh
#看进程,如果返回一大串东西,说明tomcat启动成功。
[root@iZbp1dssknxftmjczbtpndZ ~]# ps -ef|grep tomcat
运行SpringBoot:
[root@iZbp1dssknxftmjczbtpndZ ~]# ls
Desktop images imooc_mall_online.sql install.sh logs mall-0.0.1-SNAPSHOT.jar null
[root@iZbp1dssknxftmjczbtpndZ ~]# java -jar mall-0.0.1-SNAPSHOT.jar
===============================================
当把SpringBoot项目的jar包部署到linux服务器中,启动SpringBoot项目,却无法正常访问,这是怎么回事呢?
主要的原因是端口号被Linux的firewall防火墙拦截掉了
解决办法:
1、查询已开启的端口列表:firewall-cmd --list-ports
不出意外的话,是啥也没有,这也表示,所有端口都未放行,当然springboot项目的80端口也被拦截掉了
2、firewall-cmd --zone=public --add-port=1-12345/tcp --permanent
将端口1~12345全都开启
3、重启防火墙
service firewalld restart
4、重新运行springboot项目
5、成功访问
===============================================
server.port=8083
spring.datasource.name=imooc_mall_datasource
spring.datasource.url=jdbc:mysql://47.98.225.105/imooc_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=Panchunyao123!
mybatis.mapper-locations=classpath:mappers/*.xml
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
file.upload.dir=/root/images/
file.upload.ip=47.98.225.105
icode=ABCDE
SpringBoot上传图片,图片不能及时显示问题_springboot项目上传照片到uoload 不会实时更新-CSDN博客
Springboot 上传图片到项目路径下不能访问,需要重启_springboot能上传图片到win11中但无法访问图片-CSDN博客
关于springboot项目图片上传到本地,必须重启之后才能访问的解决方案_springboot上传图片需要重启-CSDN博客
关于IDEA2022开启热部署没有compiler.automake.allow.when.app.running的解决方案-CSDN博客
Linux部署SpringBoot项目无法访问问题_linux springboot项目启动外面不能访问-CSDN博客
linux服务器部署SpringBoot项目并查看项目运行日志_linux 怎么实时查看springboot 日志-CSDN博客
线上发布报数据库没有连接成功,麻烦帮忙看…-慕课网 (imooc.com)
linux安装mysql 8 数据库(保姆级)_linux安装mysql8-CSDN博客
慕慕生鲜详细步骤全部打通(从无到上线)-CSDN博客
mysql8.0密码=> root & Panchunyao123!