后端从0搭建项目
1.首先创建一个spring boot项目,先不用勾选任何依赖或插件。
2.删除sky项目下的多余目录
3.在sky项目下新建三个子模块,只留下sky-server的一个Spring Boot 应用程序的入口类 SkyServiceApplication
sky下的pom文件,pom文件的作用是导入第三方库的依赖坐标,也就是可以使用别人写好的一些东西。
<?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>
<groupId>com.tang</groupId>
<artifactId>sky</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 这段代码的主要作用是让当前项目继承Spring Boot的父项目,从而简化项目的配置和依赖管理。-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.3</version>
</parent>
<!-- 父模塊下的三個子模塊-->
<modules>
<module>sky-common</module>
<module>sky-pojo</module>
<module>sky-server</module>
</modules>
<!-- 这个标签用于定义项目中使用的各种属性和版本号。这些属性可以在整个 pom.xml 文件中引用,从而简化版本管理。-->
<properties>
<mybatis.version>2.3.0</mybatis.version>
<lombok.version>1.18.26</lombok.version>
<fastjson.version>1.2.76</fastjson.version>
<commons-lang.version>2.6</commons-lang.version>
<druid.version>1.2.1</druid.version>
<pagehelper.version>1.4.2</pagehelper.version>
<knife4j.version>3.0.2</knife4j.version>
<aspectj.version>1.9.7</aspectj.version>
<jjwt.version>0.9.1</jjwt.version>
<aliyun.oss.version>3.15.1</aliyun.oss.version>
<jaxb.version>2.3.1</jaxb.version>
<poi.version>3.16</poi.version>
<wechatpay.version>0.4.8</wechatpay.version>
</properties>
<!--
这段代码定义了一系列依赖项,涵盖了从数据库操作(MyBatis)、代码简化(Lombok)
、JSON处理(Fastjson)、字符串处理(Commons Lang) 数据库连接池(Druid)、
分页插件(PageHelper)、API文档生成(Knife4j)、AOP支持(AspectJ)、JWT生成与验证(JJWT)、
云存储(Aliyun OSS)、XML绑定(JAXB)、文档处理(Apache POI)到支付服务(微信支付)等多个方面。
这些依赖项共同构成了一个功能丰富的Spring Boot项目的基础架构,支持从数据访问到业务逻辑再到外部服务的完整开发流程。-->
<dependencyManagement>
<dependencies>
<!-- 这个依赖项的主要作用是为 Spring Boot 项目提供 MyBatis 的集成支持,-->
<!-- 简化了 MyBatis 在 Spring Boot 项目中的配置和使用-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>${wechatpay.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
sky-common模块下的pom
<?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>
<artifactId>sky-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 继承sky父项目-->
<parent>
<artifactId>sky</artifactId>
<groupId>com.tang</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 导入依赖,版本号在父项目中统一管理,本模块独有的依赖要写版本号-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--支持配置属性类,yml文件中可以提示配置项-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<!--微信支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
</project>
sky-pojo模块下的pom
<?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>
<artifactId>sky-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 导入sky父项目依赖坐标-->
<parent>
<artifactId>sky</artifactId>
<groupId>com.tang</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 帮助将Java对象与JSON数据进行相互转换-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
sky-server模块下的pom
<?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>
<artifactId>sky-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 导入父工程依赖坐标-->
<parent>
<artifactId>sky</artifactId>
<groupId>com.tang</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<!-- 导入兄弟工程依赖坐标,这样就可以继承兄弟工程独有的依赖-->
<dependency>
<groupId>com.tang</groupId>
<artifactId>sky-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.tang</groupId>
<artifactId>sky-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>compile</scope>
</dependency>
<!-- 数据库连接: 这个依赖项的主要功能是提供与 MySQL 数据库的连接能力-->
<!-- 。它允许 Java 应用程序在运行时与 MySQL 数据库进行交互,执行查询、插入、更新等操作。-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<!-- poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
</dependencies>
<!-- 这段代码的主要功能是配置 Maven 构建过程中使用的 Spring Boot 插件。Spring Boot 插件提供了许多有用的功能,-->
<!-- 如将项目打包为可执行的 JAR 文件、运行 Spring Boot 应用程序等。通过配置这个插件,开发者可以更方便地使用 Maven-->
<!-- 构建和运行 Spring Boot 项目。-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.创建如图所示的建构目录
5.yml文件,作用:配置项目启动的端口,数据库信息等
application.yml:起作用的yml,可以通过 profiles: active: dev来控制使用哪个版本的yml
application-dev.yml:开发版本的yml
配置数据库信息
sky:(项目名)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver(固定写法)
host: localhost(本机)
port: 3306(固定端口)
database: sky_take_out(数据库名字)
username: root(写自己的数据库账号名)
password: ****(写自己的数据库密码)
application.yml:
server:
port: 8080
spring:
# 由于SpringBoot整合Swagger时,SpringBoot版本与 springfox版本不兼容
# ant_path_matcher 是一个具体的值,表示使用 Ant 风格的路径匹配策略。
# Ant 风格的路径匹配策略允许使用通配符(如 * 和 **)来匹配 URL 路径。
mvc:
pathmatch:
matching-strategy: ant_path_matcher
# Spring Boot支持多个配置文件,例如dev(开发环境)、test(测试环境)、prod(生产环境)等。
# 每个配置文件可以包含不同的配置设置,例如数据库连接、日志级别、缓存配置等。
profiles:
active: dev
# 默认情况下,Spring容器是不允许循环依赖的,因为这可能导致无限递归和内存溢出等问题。
# 将 allow-circular-references 设置为 true 表示允许Spring容器在创建Bean时处理循环依赖。
# 免由于循环依赖导致的应用程序启动失败
main:
allow-circular-references: true
# 配置数据库连接信息,属性值从application-dev.yml中读取。
datasource:
druid:
driver-class-name: ${sky.datasource.driver-class-name}
url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}
username: ${sky.datasource.username}
password: ${sky.datasource.password}
mybatis:
# 指定了MyBatis的Mapper XML文件的位置,即在类路径下的mapper目录中查找所有的XML文件
mapper-locations: classpath*:mapper/*.xml
指定了类型别名包为com:
sky:
# entity,这样在该包中的类可以使用简短的类名作为别名:
type-aliases-package: com.sky.entity
# 驼峰命名自动映射:开启了驼峰命名自动映射功能,使得数据库中的下划线命名(如user_name)
# 可以自动映射到Java对象的驼峰命名属性(如userName)。
configuration:
map-underscore-to-camel-case: true
# 这段代码的主要功能是配置Spring Boot应用程序的日志记录级别。具体来说,它为 com.sky 包下的不同子包
# (controller、service、mapper)设置了不同的日志级别:
#
# com.sky.controller 和 com.sky.service 包中的类将记录 info 级别及以上的日志信息。
# com.sky.mapper 包中的类将记录 debug 级别及以上的日志信息。
# 通过这种方式,开发者可以根据不同的包和类来控制日志的详细程度,从而更好地调试和监控应用程序的运行状态。
logging:
level:
com:
sky:
controller: info
service: info
mapper: debug
# 配置jwt令牌的密钥和有效期,并指定令牌的名称。
sky:
jwt:
admin-secret-key: tang
admin-ttl: 72000000
admin-token-name: token
配置文件:
JwtUtil工具类:作用,生成jwt令牌和解析jwt令牌。
package com.sky.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
/**
* 生成JWT
*
* @param secretKey
* @param ttlMillis
* @param claims
* @return
* @throws UnsupportedEncodingException
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) throws UnsupportedEncodingException {
//1.指定签名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//2.JWT过期时间=现在时间+ttlMillis
long expMillis = System.currentTimeMillis() + ttlMillis;
//转换为Date类型
Date exp = new Date(expMillis);
//3.生成JWT,claim是自定义的键值对
String compact = Jwts.builder()
.setClaims(claims)
.setExpiration(exp)
.signWith(signatureAlgorithm, secretKey.getBytes("UTF-8"))
.compact();
return compact;
}
/**
* 解析JWT
*
* @param secretKey
* @param token
* @return
*/
public static Claims parseJwt(String secretKey, String token) {
//1.给出秘钥和要解析的token
Claims claims = Jwts.parser()
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token).getBody();
return claims;
}
}
interception拦截器:作用,每次请求后端都要拦截下来校验是否放行。并且需要在webMvcConfiguration文件中注册才能生效。
package com.sky.interception;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
// 该拦截器用于验证管理员JWT的有效性
public class JwtAdminInterception implements HandlerInterceptor {
private JwtProperties jwtProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果不是controller方法,放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
//从前端的请求头中获取token
String token = request.getHeader(jwtProperties.getAdminTokenName());
try {
//解析token,如果解析失败,则返回401
Claims claims = JwtUtil.parseJwt(jwtProperties.getAdminSecretKey(), token);
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
}
webMvcConfiguration配置文件:作用:WebMvcConfiguration 是 Spring MVC 框架中的一个重要配置类,主要用于定制和配置 Spring MVC 的行为。通过这个配置类,开发者可以自定义 Spring MVC 的各种组件和行为,例如视图解析器、拦截器、消息转换器等。
package com.sky.config;
import com.sky.interception.JwtAdminInterception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtAdminInterception jwtAdminInterception;
// 注册拦截器
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtAdminInterception)
//拦截/admin下的所有请求
.addPathPatterns("/admin/**")
//排除/admin/employee/login请求,即登录请求
.excludePathPatterns("/admin/employee/login");
}
/**
* 生成接口文档,两步:new ApiInfo描述文档的基本信息
* 2.new Docket描述文档的详细信息,包括要扫描的包、标题、描述、版本等信息
* @return
*/
public Docket docket1() {
log.info("準備生成接口文檔...");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("蒼穹外賣管理系統API")
.description("蒼穹外賣管理系統API")
.version("1.0")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("管理端")
.apiInfo(apiInfo)
.select()
//指定要扫描的包
.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
.build();
return docket;
}
/**
* 静态资源映射,访问地址:http://localhost:8080/doc.html会自动映射到Swagger2帮我们生成的API文档页面,
* 固定在classpath:/META-INF/resources/webjars包下
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Result:作用:固定的返回给前端的对象
package com.sky.result;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
private Integer code;// 1:成功 0:失败
private String msg;// 错误信息
private T data;// 返回数据
public static <T> Result success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<T>();
result.code = 1;
result.data = data;
return result;
}
public static <T> Result<T> error(String msg) {
Result<T> result = new Result<T>();
result.code = 0;
result.msg = msg;
return result;
}
}
contant:自定义的一些常量
package com.sky.constant;
public class MessageConstant {
public static final String PASSWORD_ERROR = "密码错误";
public static final String ACCOUNT_NOT_FOUND = "账号不存在";
public static final String ACCOUNT_LOCKED = "账号被锁定";
public static final String UNKNOWN_ERROR = "未知错误";
}
package com.sky.constant;
public class JwtClaimsConstant {
public static final String EMP_ID = "empId";
public static final String USER_ID = "userId";
public static final String PHONE = "phone";
public static final String USERNAME = "username";
public static final String NAME = "name";
}
exception:异常类,定义各种异常
BaseException:父异常
package com.sky.exception;
public class BaseException extends RuntimeException{
public BaseException() {
}
public BaseException(String message) {
super(message);
}
}
子异常继承BaseException
最后一步,全局异常处理器,比如throw AccountNotFoundException,她就捕获它,并以 Result对象给
前端返回数据(e.getMessage)),至此,后端基础项目搭建完成,可以开始完成第一个接口–》用户登录
package com.sky.handler;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler
public Result exceptionHandler(Exception e) {
log.error("全局异常处理", e);
return Result.error(e.getMessage());
}
}
管理端模块
新增用户
1.controller:
@ApiOperation("用戶新增")
@PostMapping
public Result save(@RequestBody EmployeeDTO employeeDTO){
employeeService.save(employeeDTO);
return Result.success();
}
2.service接口:
void save(EmployeeDTO employeeDTO);
3.serviceImpl:
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//把前端传来的dto复制到employee对象中,
// 所以employee对象中还有password,和status属性,create_time,update_time,update_user和update_User属性还赋值值
//password和status属性赋值默认值,create_time,update_time,update_user和update_User属性用切面来自动填充
BeanUtils.copyProperties(employeeDTO,employee);
//password加密并赋值给employee对象
String password = DigestUtils.md5DigestAsHex(PasswordConstant.PASSWORD_DEFAULT.getBytes());
employee.setPassword(password);
//status赋值给employee对象
employee.setStatus(StatusConstant.ENABLE);
employeeMapper.save(employee);
}
4.mapper接口:
void save(EmployeeDTO employeeDTO);
5.mapper.xml:
<insert id="save">
insert into employee
(name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user)
values (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{status}, #{createTime},
#{updateTime}, #{createUser}, #{updateUser})
</insert>
附:
1.枚举类:
package com.sky.enumeration;
public enum OperationType {
UPDATE,
INSERT
}
2.注解类:
package com.sky.annotation;
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//表示用在方法上(Method)添加注解
@Target(ElementType.METHOD)
//表示注解的生命周期,在运行时期
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
3.线程常量类:
package com.sky.context;
public class BaseContext {
// 线程变量,用于保存当前请求的id,不同用户开启一条线程来区别不同用户
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
// 设置当前请求用户的id
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
// 获取当前请求用户的id
public static Long getCurrentId() {
return threadLocal.get();
}
// 清除当前请求用户的id
public static void removeCurrentId() {
threadLocal.remove();
}
}
4.在拦截器拦截用户请求时把用户id(empId)放入线程中
package com.sky.interception;
import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
// 该拦截器用于验证管理员JWT的有效性
public class JwtAdminInterception implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果不是controller方法,放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
//从前端的请求头中获取token
String token = request.getHeader(jwtProperties.getAdminTokenName());
try {
//解析token,如果解析失败,则返回401
Claims claims = JwtUtil.parseJwt(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前登录用户的empId为:{}", empId);
BaseContext.setCurrentId(empId);
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
}
5.切面类aspect:
package com.sky.aspect;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
//切入點,即哪些地方要執行自動填充
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {
}
//前置通知,在原來的方法運行前,先執行以下代碼
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
log.info("開始自動填寫");
//獲取簽名對象,例如:com.sky.mapper.EmployeeMapper.save(Employee employee)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//獲取簽名對象的方法(save)的上面注解
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);
//獲取操作類型
OperationType operationType = annotation.value();
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
//獲取操作實體對象(例如:Employee)
Object entity = args[0];
//準備填寫時間和操作人
LocalDateTime now = LocalDateTime.now();
//從當前綫程中獲取操作人id
Long currentId = BaseContext.getCurrentId();
//通過反射,將時間和操作人填入實體對象
if (operationType == OperationType.INSERT) {
try {
//獲取實體對象的createTime、updateTime、createUser、updateUser方法
Method createTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method updateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method createUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method updateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通過反射,將時間和操作人填入實體對象
createTime.invoke(entity, now);
updateTime.invoke(entity, now);
createUser.invoke(entity, currentId);
updateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
try {
Method updateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method updateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
updateTime.invoke(entity, now);
updateUser.invoke(entity, currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
添加消息转换器
package com.sky.json;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public JacksonObjectMapper() {
super();
//收到未知屬性時不報異常,**否則在用@RequestBody接受前端數據報錯**
this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列時,屬性不存在的兼容處理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//这里创建了一个 SimpleModule 的实例,SimpleModule 是 Jackson 提供的一个模块化功能,
// 允许你添加自定义的序列化器(Serializer)和反序列化器(Deserializer)。
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
/**
* ,这个类扩展了 Spring MVC 的消息转换器
*
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//这里创建了一个 MappingJackson2HttpMessageConverter 的实例。这个转换器是 Spring 提供的,
// 主要用于将 Java 对象转换为 JSON 格式,并将 JSON 数据转换为 Java 对象。
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//将一个自定义的 JacksonObjectMapper 设置为转换器的 ObjectMapper。
// 这允许开发者定制 JSON 的序列化和反序列化行为,比如处理日期格式、字段命名策略等
converter.setObjectMapper(new JacksonObjectMapper());
//将刚创建并配置好的转换器添加到 converters 列表的第一个位置。这样做会确保在进行消息转换时优先使用这个自定义的转换器。
converters.add(0, converter);
}
阿里云文件上传5步走
1.yml文件配置阿里云信息(要配置自己阿里云Oss的信息)
sky:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
host: localhost
port: 3306
database: sky_take_out
username: root
password: 1234
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: LTAI5tBLYppXGW46Lx*****
access-key-secret: KVuiJrPcHrp58qbKZkt******
bucket-name: sky-itcast-kaka
2.properties包
作用:把yml件中的oss配置转换为java对象并交给bean容器管理
package com.sky.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
//提取yml中的sky.alioss开头的配置
@ConfigurationProperties(prefix="sky.alioss")
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
3.查看阿里云官方给的上传文件的示例代码并改造一下
package com.sky.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtils {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
public String upload(byte[] bytes, String fileName) {
// OSSClientBuilder是一个构建OSS客户端的工具类。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
//上传对象
ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(bytes));
//关闭OSSClient。
ossClient.shutdown();
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(fileName);
//返回文件访问路径
return stringBuilder.toString();
}
}
4.config包
作用:初始化AliOssUtils类,即以有参构造new 一个AliOssUtils对象交给bean管理,方便自动装配
package com.sky.config;
import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AliOssConfig {
@Bean
//这个注解是 Spring 提供的条件注解,表示仅在 Spring 容器中不存在相同类型的 Bean 时,
// 才会创建并注册这个 Bean。这样做的目的可以防止 Bean 的重复定义。
@ConditionalOnMissingBean
public AliOssUtils aliOssUtils(AliOssProperties aliOssProperties) {
return new AliOssUtils(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName());
}
}
5.controller类
package com.sky.controller.admin;
import com.sky.constant.MessageConstant;
import com.sky.exception.NoNullException;
import com.sky.result.Result;
import com.sky.utils.AliOssUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@RestController
@Api(tags = "公共接口")
@Slf4j
@RequestMapping("/admin/common/upload")
public class CommonController {
@Autowired
private AliOssUtils aliOssUtils;
@PostMapping
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
//截取最後一個.的後面的字串
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//UUID生成随机字符串+文件后缀作爲新的文件名,防止文件名重复
String uuid = UUID.randomUUID().toString();
String newFilename = uuid + "." + suffix;
String filename = null;
try {
filename = aliOssUtils.upload(file.getBytes(), newFilename);
} catch (IOException e) {
throw new NoNullException(MessageConstant.FILE_UPLOAD_ERROR);
}
return Result.success(filename);
}
}
设置店铺营业状态
1.导入依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置yml文件
redis:
host: localhost
port: 6379
password: 123456
database: 10
3.配置config(非必要),把序列化器指定为String类型的,方便我们看key
package com.sky.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
// 配置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置value的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
4.controller,注意,RestController一定要其花名,否知会Bean重复
package com.sky.controller.user;
import com.sky.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController("userShopController")
@Slf4j
@Api(tags = "用戶端店鋪接口")
@RequestMapping("/user/shop")
public class ShopController {
private static final String SHOP_STATUS = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/status")
@ApiOperation("獲取營業狀態")
public Result<Integer> getStatus() {
ValueOperations valueOperations = redisTemplate.opsForValue();
Integer status = (Integer) valueOperations.get(SHOP_STATUS);
log.info("獲取店鋪狀態: {}", status == 1 ? "營業中" : "打烊中");
return Result.success(status);
}
}
用戶端模块
用户登录
1.yml配置小程序信息并提取成bean
wechat:
appid: wxb2439a7******
secret: c2906d5c0ca244c*******
package com.sky.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {
private String appid; //小程序的appid
private String secret; //小程序的秘钥
}
2.controller
package com.sky.controller.user;
import com.sky.constant.JwtClaimsConstant;
import com.sky.dao.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
@RestController
@Slf4j
@Api(tags = "用户管理")
@RequestMapping("/user/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")
@ApiOperation("登錄")
public Result<UserLoginVO> wxLogin(@RequestBody UserLoginDTO userLoginDTO) throws UnsupportedEncodingException {
User user = userService.login(userLoginDTO);
HashMap<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID, user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openId(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
3.serviceImpl,按照微信官方提供的步骤获取openId
package com.sky.service.Impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dao.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.vo.UserLoginVO;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
//微信服务接口地址
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
public User login(UserLoginDTO userLoginDTO) {
String openId = getOpenId(userLoginDTO.getCode());
if (openId == null) {
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
User user = userMapper.getByOpenId(openId);
if (user == null) {
user = User.builder()
.openid(openId)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
return user;
}
private String getOpenId(String code) {
//调用微信接口服务,获得当前微信用户的openid
Map<String, String> map = new HashMap<>();
map.put("appid", weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code", code);
map.put("grant_type", "authorization_code");
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
String result = "";
CloseableHttpResponse response = null;
try {
URIBuilder builder = new URIBuilder(WX_LOGIN);
if (map != null) {
for (String key : map.keySet()) {
builder.addParameter(key, map.get(key));
}
}
URI uri = builder.build();
//创建GET请求
HttpGet httpGet = new HttpGet(uri);
//发送请求
response = httpClient.execute(httpGet);
//判断响应状态
if (response.getStatusLine().getStatusCode() == 200) {
result = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
JSONObject jsonObject = JSON.parseObject(result);
String openid = jsonObject.getString("openid");
return openid;
}
}
缓存数据
1.spring cache起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId> <version>2.7.3</version>
</dependency>
2.启动类加注解:@EnableCaching
3.常见注解
说明: key的写法如下
#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;
#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;
#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数
的id属性作为key ;