本系列内容完成Spring Boot框架的电商生鲜网站开发的完整案例,前后端分离开发的案例,先开发后端接口后开发前端,最后部署等待。
Spring Boot项目开发准备
文章目录
IDEA web开发必备插件
Maven Helper 与Maven一起工作的必备插件
Free Mybatis Tool 帮助我们跳转和识别Mapper中的一些语法错误
2022.7.7 – 发现MybaitsX也不错很好用,方便XML跳转和SQL生成
https://baomidou.com/pages/ba5b24/#%E5%8A%9F%E8%83%BD
Postman安装——接口自测
帮助我们开发时接口自测的调试工具,免费
MySQL可视化工具——mac平台
Sequel pro for mac是Mac os平台上的一款帮助用户快速连接SQL
数据库
的开源免费Mac软件
http://sequelpro.com/test-builds
在连接MySQL时可能会出现问题:MySQL said: Authentication plugin ‘caching_sha2_password’ cannot be loaded
这会初始化数据库,数据库有数据的话请一定备份😭
打开系偏好设置-MySQL-Initialize Database-输入设置的密码-勾选Use Legacy Password Encryption-启动MySQL
数据库设计
案例:
/*
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80026
Source Host : localhost:3306
Source Schema : fresh_mall
Target Server Type : MySQL
Target Server Version : 80026
File Encoding : 65001
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for mall_cart
-- ----------------------------
DROP TABLE IF EXISTS `mall_cart`;
CREATE TABLE `mall_cart` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '购物车id',
`product_id` int NOT NULL COMMENT '商品id',
`user_id` int NOT NULL COMMENT '用户id',
`quantity` int NOT NULL DEFAULT '1' COMMENT '商品数量',
`selected` int NOT NULL DEFAULT '1' COMMENT '是否已勾选:0代表未勾选,1代表已勾选',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='购物车';
-- ----------------------------
-- Table structure for mall_category
-- ----------------------------
DROP TABLE IF EXISTS `mall_category`;
CREATE TABLE `mall_category` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '分类目录名称',
`type` int NOT NULL COMMENT '分类目录级别,例如1代表一级,2代表二级,3代表三级',
`parent_id` int NOT NULL COMMENT '父id,也就是上一级目录的id,如果是一级目录,那么父id为0',
`order_num` int NOT NULL COMMENT '目录展示时的排序',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品分类 ';
-- ----------------------------
-- Table structure for mall_order
-- ----------------------------
DROP TABLE IF EXISTS `mall_order`;
CREATE TABLE `mall_order` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`order_no` varchar(128) NOT NULL DEFAULT '' COMMENT '订单号(非主键id)',
`user_id` int NOT NULL COMMENT '用户id',
`total_price` int NOT NULL COMMENT '订单总价格',
`receiver_name` varchar(32) NOT NULL COMMENT '收货人姓名快照',
`receiver_mobile` varchar(32) NOT NULL COMMENT '收货人手机号快照',
`receiver_address` varchar(128) NOT NULL DEFAULT '' COMMENT '收货地址快照',
`order_status` int NOT NULL DEFAULT '10' COMMENT '订单状态: 0用户已取消,10未付款(初始状态),20已付款,30已发货,40交易完成',
`postage` int DEFAULT '0' COMMENT '运费,默认为0',
`payment_type` int NOT NULL DEFAULT '1' COMMENT '支付类型,1-在线支付',
`delivery_time` timestamp NULL DEFAULT NULL COMMENT '发货时间',
`pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
`end_time` timestamp NULL DEFAULT NULL COMMENT '交易完成时间',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表;';
-- ----------------------------
-- Table structure for mall_order_item
-- ----------------------------
DROP TABLE IF EXISTS `mall_order_item`;
CREATE TABLE `mall_order_item` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`order_no` varchar(128) NOT NULL DEFAULT '' COMMENT '归属订单id',
`product_id` int NOT NULL COMMENT '商品id',
`product_name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称',
`product_img` varchar(128) NOT NULL DEFAULT '' COMMENT '商品图片',
`unit_price` int NOT NULL COMMENT '单价(下单时的快照)',
`quantity` int NOT NULL DEFAULT '1' COMMENT '商品数量',
`total_price` int NOT NULL DEFAULT '0' COMMENT '商品总价',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单的商品表 ';
-- ----------------------------
-- Table structure for mall_product
-- ----------------------------
DROP TABLE IF EXISTS `mall_product`;
CREATE TABLE `mall_product` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '商品主键id',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`image` varchar(500) NOT NULL DEFAULT '' COMMENT '产品图片,相对路径地址',
`detail` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '商品详情',
`category_id` int NOT NULL COMMENT '分类id',
`price` int NOT NULL COMMENT '价格,单位-分',
`stock` int NOT NULL COMMENT '库存数量',
`status` int NOT NULL DEFAULT '1' COMMENT '商品上架状态:0-下架,1-上架',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表';
-- ----------------------------
-- Table structure for mall_user
-- ----------------------------
DROP TABLE IF EXISTS `mall_user`;
CREATE TABLE `mall_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(50) NOT NULL COMMENT '用户密码,MD5加密',
`personalized_signature` varchar(50) NOT NULL DEFAULT '' COMMENT '个性签名',
`role` int NOT NULL DEFAULT '1' COMMENT '角色,1-普通用户,2-管理员',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表 ';
SET FOREIGN_KEY_CHECKS = 1;
项目初始化–依赖引入pom
pom.xml sprintboot版本2.2.1.RELEASE
<!--引入依赖-->
<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>
<--插件配置-->
<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>
自动创建实体类和mapper
利用mybatis-generator自动创建实体类和mapper
resources下创建generatorConfig.xml
配置mysql-connector-java-8.0.18.jar在你电脑存放的路径,我这里都放入resources
配置数据库连接
配置生成Model类存放位置
配置生成mapper映射文件存放位置
配置生成Dao类存放位置
配置生成对应表及类名
<?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/cat/Documents/fresh-mall/src/main/resources/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/fresh_mall?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"
userId="root"
password="root1024">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="com.learn2333.freshmall.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.learn2333.freshmall.model.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成对应表及类名-->
<table schema="root" tableName="mall_cart" domainObjectName="Cart"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_category" domainObjectName="Category" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_order" domainObjectName="Order" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_order_item" domainObjectName="OrderItem"
enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_product" domainObjectName="Product" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="mall_user" domainObjectName="User" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
点击右侧mavan中的插件–这个插件是因为上面pom中配置的才生效的
会自动生成mapper和实体类
在生成的mapper接口和mapper.xml文件中左侧行号右边会有互相跳转的箭头,这是配置的IDEA插件Free Mybatis Tool 生效了
配置项目的数据库连接
application.properties
server.port=8080
spring.datasource.name=fresh_mall
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/fresh_mall?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root1024
#mapper的位置
mybatis.mapper-locations=classpath:mappers/*.xml
Application启动类配置dao mapper接口的注解路径
@MapperScan(basePackages = "com.learn2333.freshmall.model.dao")
创建测试类
controller.UserController
package com.learn2333.freshmall.controller;
import com.learn2333.freshmall.model.pojo.User;
import com.learn2333.freshmall.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 {
@Autowired
UserService userService;
@GetMapping("/test")
@ResponseBody
public User personalPage() {
return userService.getUser();
}
}
service.UserService接口
package com.learn2333.freshmall.service;
import com.learn2333.freshmall.model.pojo.User;
public interface UserService {
User getUser();
}
service.impl.UserServiceImpl实现类
package com.learn2333.freshmall.service.impl;
import com.learn2333.freshmall.model.dao.UserMapper;
import com.learn2333.freshmall.model.pojo.User;
import com.learn2333.freshmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User getUser() {
return userMapper.selectByPrimaryKey(1);
}
}
运行后访问test即可获得数据,连接成功
但是此时在代码中:引入的mapper会被标红,但仍可正常运行
@Autowired
UserMapper userMapper;
这是因为编译器无法自动识别mapper,因为mapper是在springboot入口
@MapperScan(basePackages = "com.learn2333.freshmall.model.dao")
配置的,这是为了给mybatis识别用的,而编译器无法识别,解决方案可以在dao下的mapper接口上增加注解@Repositor,这样IDEA就会认为@Autowired进来的mapper是一个资源。
log4j2日志的引入
日志级别:
error
, warn,
info
,
debug
,trace(依次向下级别递减)
排除之前的日志组件Logback依赖
pom.xml
<!--在spring-boot-starter-web使用exclusions排除自带的组件-->
<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-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
配置日志组件
log4j2.xml
配置日志文件保存的位置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="fatal">
<Properties>
<!-- 日志文件保存位置 mac,linux系统下此目录代表当前登录用户目录下的logs目录-->
<Property name="baseDir" value="${sys:user.home}/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级别日志文件输出 此处引用的${baseDir}是上面定义过的name的value-->
<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>
<logger name="org.springframework" level="info"
additivity="true">
<AppenderRef ref="Console" />
<AppenderRef ref="MyFile" />
</logger>
<!-- 控制台打印的级别配置 -->
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="debug_appender"/>
<AppenderRef ref="info_appender"/>
<AppenderRef ref="error_appender"/>
</Root>
</Loggers>
</Configuration>
运行项目,可以看到在配置好的文件路径下生成了日志
打开控制台使用
tail -f 文件名
可以打印出文件新增加的内容
访问一个请求,内容也会相应变化,可以用这个方法调试程序。
AOP统一处理Web请求日志
是对系统健壮性的一个保护,利用fitter把每一个请求打印出来,提高开发和调试的效率
目的:创建fitter,把请求和响应信息打印出来
pom引入aop
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
fitter.WebLogAspect.java 直接创建fitter类,利用注解配合方法实现自定义请求和响应日志的输出
package com.learn2333.freshmall.fitter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
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 {
private final Logger log = LoggerFactory.getLogger(WebLogAspect.class);
//指定拦截点
@Pointcut("execution(public * com.learn2333.freshmall.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
//收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录日志
//url
log.info("URL : " + request.getRequestURI().toString());
//请求类型
log.info("HTTP_METHOD : " + request.getMethod());
//ip
log.info("IP : " + request.getRemoteAddr());
//类的方法
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
//参数
log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "res", pointcut = "webLog()")
public void doAfterReturn(Object res) throws JsonProcessingException {
//处理完请求,返回内容
//writeValueAsString是fastjson提供的一个将对象转成json格式的工具
log.info("RESPONSE : " + new ObjectMapper().writeValueAsString(res));
}
}