企业级微服务架构项目-尚上优选
一 项目概述
1 项目介绍
社区团购是真实居住社区内居民团体的一种互联网线上线下购物消费行为,是依托真实社区的一种区域化、小众化、本地化、网络化的团购形式。简而言之,它是依托社区和团长社交关系实现生鲜商品流通的新零售模式。
比如:
**美团优选,**是美团旗下的一个社区团购平台,通过自建和加盟的方式,在全国建立了大仓、网格仓、线下服务门店的物流配送体系。美团优选卖的东西也特别的全,蔬菜、水果、肉类、蛋类,然后海鲜等等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCkHOBpu-1686702899950)(images\image-20230322095158332.png)]
**多多买菜,**它是拼多多旗下的产品,拼多多现在已经是全中国最大的生鲜电商了,它和全国超过一千个农产品产区达成合作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPm9xxjL-1686702899953)(images\image-20230322095541897.png)]
2 业务流程
从具体模式看,主要围绕平台、团长、用户三个角色展开:
1、团长(如社区宝妈、便利店老板等)创建一个群,提前发布优惠商品的链接供用户购买,团长从中抽取佣金;
2、用户提前一天下单;
3、平台在收集好订单之后,调动供应链,从仓库发货到自提点(团长家或者便利店);
4、用户前往自提点提货
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10mk6nvr-1686702899953)(images\001.png)]
3 功能架构
功能架构分为三层
1、前台会员应用层
2、前台团长应用层
3、基础模块支撑层
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49IeTRkL-1686702899954)(images\002.png)]
4 技术架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPaXomBF-1686702899954)(images\003.png)]
5 核心技术
SpringBoot:简化新Spring应用的初始搭建以及开发过程
SpringCloud:基于Spring Boot实现的云原生应用开发工具,SpringCloud使用的技术:(Spring Cloud Gateway、Spring Cloud OpenFeign、Spring Cloud Alibaba Nacos等)
MyBatis-Plus:持久层框架
Redis:缓存数据库
Redisson:基于redis的Java驻内存数据网格,实现分布式锁
RabbitMQ:消息中间件
ElasticSearch +Kibana: 全文检索服务器 +可视化数据监控
ThreadPoolExecutor:线程池来实现异步操作,提高效率
OSS:文件存储服务
Knife4j(Swagger):Api接口文档工具
Nginx:负载均衡
MySQL:关系型数据库
微信支付
微信小程序
Docker:容器技术
DockerFile:管理Docker镜像命令文本
6 项目模块
最终服务器端架构模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Yljs2Z2-1686702899969)(images\image-20230427120227325.png)]
guigu-ssyx-parent:父工程,根目录,管理子模块:
common:公共类父模块
common-util:核心工具类
service-util:service模块工具类
rabbit-util:RabbitMQ工具类
model:实体类模块
service:系统微服务模块
service-client:系统远程调用封装模块
7 其他资源
实体类、数据库脚本、项目中使用的工具类、前端源码等都在资料文件夹中,实体类直接复制到model模块,后续不做说明。
二 前后端分离开发概述
1 什么是前后端分离开发
前后端分离开发,就是在项目开发过程中,对于前端的代码专门由前端的开发人员开发,后端代码由后端人员负责,这样可以做到分工明确、各司其职,进而提高开发效率,前后端代码并行开发,加快项目的开发进度。目前前后端分离被各大公司使用,成为项目开发的主流开发方式。前后端分离开发后,工程结构也会发生变化,即前后端代码不会混在同一个maven工程中,而是分为前端工程和后端工程。
在美国等互联网环境比较发达的国家项目开发的分工协作更为明确,整个项目开发分为前端、中间层和后端三个开发阶段,这三个阶段分别由三个或者更多的人来协同完成。国内的大部分互联网公司只有前端工程师和后端工程师,中间层的工作有的由前端来完成,有的由后端来完成。
2 开发流程介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sabA5Pyc-1686702899970)(images\0065.png)]
前后端分离开发过程中,前端人员和后端人员要进行配合来共同完成一个任务。这个时候需要使用到接口。
接口(API接口):是一个http的请求地址,主要是定义:请求路径、请求方式、请求参数、响应数据等内容。
(1)后端编写和维护接口文档,在 API 变化时更新接口文档
(2)后端根据接口文档进行接口开发
(3)前端根据接口文档进行开发
(4)开发完成后联调和提交测试
后端开发人员为前端提供接口的同时,还需同时提供接口的说明文档。但我们的代码总是会根据实际情况来实时更新,这个时候有可能会忘记更新接口的说明文档,造成一些不必要的问题。我们可以通过一些工具生成接口文档,做到文档实时更新。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务的接口文档。
三 搭建后端环境
1 搭建项目结构
项目结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuGd26fY-1686702899971)(images\014.png)]
1.1 搭建父工程guigu-ssyx-parent
管理子模块及依赖
GroupId:com.atguigu
ArtifactId:guigu-ssyx-parent
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jEj3Y3tN-1686702899972)(images\007.png)]
1.2 搭建工具类父模块common
工具类父模块,继承父工程guigu-ssyx-parent
GroupId:com.atguigu
ArtifactId:common
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8pyYbBTD-1686702899973)(images\0081.png)]
1.3 搭建工具类模块common-util
核心工具类,继承common模块
GroupId:com.atguigu
ArtifactId:common-util
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSWnoH2d-1686702899974)(images\009.png)]
1.4 搭建工具类模块service-util
service模块工具类,继承common模块
GroupId:com.atguigu
ArtifactId:service-util
搭建方式如:common-util
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M5pRHIW0-1686702899974)(images\010.png)]
1.5 搭建实体类模块model
实体类,继承guigu-ssyx-parent
搭建方式如:common
引入资料中java实体类相关代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcabnAio-1686702899975)(images\011.png)]
1.6 搭建项目模块service
service服务模块,继承guigu-ssyx-parent
搭建方式如:common
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gf6si4Fh-1686702899976)(images\013.png)]
2 配置依赖关系
2.1 guigu-ssyx-parent父工程管理依赖版本
修改guigu-ssyx-parent模块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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>guigu-ssyx-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>common</module>
<module>model</module>
<module>service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<properties>
<skipTests>true</skipTests>
<java.version>1.8</java.version>
<cloud.version>Hoxton.SR8</cloud.version>
<alibaba.version>2.2.2.RELEASE</alibaba.version>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<mysql.version>8.0.30</mysql.version>
<jwt.version>0.7.0</jwt.version>
<fastjson.version>2.0.0</fastjson.version>
<httpclient.version>4.5.1</httpclient.version>
<easyexcel.version>3.1.0</easyexcel.version>
<aliyun.version>4.1.1</aliyun.version>
<oss.version>3.9.1</oss.version>
<knife4j.version>2.0.8</knife4j.version>
<jodatime.version>2.10.1</jodatime.version>
<xxl-job.version>2.3.0</xxl-job.version>
</properties>
<!--配置dependencyManagement锁定依赖的版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun.version}</version>
</dependency>
<!--日期时间工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime.version}</version>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<layout>default</layout>
<url>https://maven.aliyun.com/repository/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
<repository>
<id>spring</id>
<url>https://maven.aliyun.com/repository/spring</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
2.2 common模块
common公共父模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>guigu-ssyx-parent</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<modules>
<module>common-util</module>
<module>service-util</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- https://doc.xiaominfo.com/knife4j/documentation/ -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--用来转换json使用 {JavaObject - json | json - JavaObject}-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
2.3 common-util模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-util</artifactId>
<!--添加依赖-->
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
2.4 service-util模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-util</artifactId>
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>common-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!-- redisson 分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.2</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
2.5 model模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>guigu-ssyx-parent</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>model</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<!--在引用时请在maven中央仓库搜索2.X最新版本号-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<scope>provided </scope>
</dependency>
<!--创建索引库的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
idea中安装lombok插件
2.6 service模块
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>guigu-ssyx-parent</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<dependencies>
<!--依赖服务的工具类-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--数据载体-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--web 需要启动项目-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 服务注册 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 流量控制 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--开发者工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
3 准备接口相关工具类
操作service-util模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlhMcnE2-1686702899977)(images\015.png)]
3.1 编写MybatisPlus配置类
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。只需简单配置即可快速进行单表的CURD操作,同时提供了自动分页,代码生成,逻辑删除等丰富的功能。
(1)配置Mapper扫描
(2)配置MyBatisPlus分页插件
package com.atguigu.ssyx.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* MybatisPlus配置类
*/
@EnableTransactionManagement
@Configuration
@MapperScan("com.atguigu.ssyx.*.mapper")
public class MybatisPlusConfig {
/**
* mp插件
*/
@Bean
public MybatisPlusInterceptor optimisticLockerInnerInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//向Mybatis过滤器链中添加分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
3.2 编写统一返回结果类
项目中我们会将接口返回的数据封装成Json数据格式,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更一致、轻松。一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容。
例如,我们的系统要求返回的基本数据格式如下:
列表:
{
"code": 200,
"message": "成功",
"data": [
{
"id": 2,
"roleName": "系统管理员"
}
],
"ok": true
}
分页:
{
"code": 200,
"message": "成功",
"data": {
"records": [
{
"id": 2,
"roleName": "系统管理员"
},
{
"id": 3,
"name": "普通管理员"
}
],
"total": 10,
"size": 3,
"current": 1,
"orders": [],
"hitCount": false,
"searchCount": true,
"pages": 2
},
"ok": true
}
没有返回数据:
{
"code": 200,
"message": "成功",
"data": null,
"ok": true
}
失败:
{
"code": 201,
"message": "失败",
"data": null,
"ok": false
}
3.2.1 创建统一返回结果状态信息类
package com.atguigu.ssyx.common.result;
import lombok.Getter;
/**
* 统一返回结果状态信息类
*
*/
@Getter
public enum ResultCodeEnum {
SUCCESS(200,"成功"),
FAIL(201, "失败"),
SERVICE_ERROR(2012, "服务异常"),
DATA_ERROR(204, "数据异常"),
ILLEGAL_REQUEST(205, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
LOGIN_AUTH(208, "未登陆"),
PERMISSION(209, "没有权限"),
ORDER_PRICE_ERROR(210, "订单商品价格变化"),
ORDER_STOCK_FALL(204, "订单库存锁定失败"),
CREATE_ORDER_FAIL(210, "创建订单失败"),
COUPON_GET(220, "优惠券已经领取"),
COUPON_LIMIT_GET(221, "优惠券已发放完毕"),
URL_ENCODE_ERROR( 216, "URL编码失败"),
ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),
FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),
FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),
SKU_LIMIT_ERROR(230, "购买个数不能大于限购个数"),
REGION_OPEN(240, "该区域已开通"),
REGION_NO_OPEN(240, "该区域未开通"),
;
private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
3.2.2 创建统一返回结果类
package com.atguigu.ssyx.common.result;
import lombok.Data;
@Data
public class Result<T> {
//状态码
private Integer code;
//信息
private String message;
//数据
private T data;
//构造私有化
private Result() { }
//设置数据,返回对象的方法
public static<T> Result<T> build(T data,ResultCodeEnum resultCodeEnum) {
//创建Resullt对象,设置值,返回对象
Result<T> result = new Result<>();
//判断返回结果中是否需要数据
if(data != null) {
//设置数据到result对象
result.setData(data);
}
//设置其他值
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
//返回设置值之后的对象
return result;
}
//成功的方法
public static<T> Result<T> ok(T data) {
Result<T> result = build(data, ResultCodeEnum.SUCCESS);
return result;
}
//失败的方法
public static<T> Result<T> fail(T data) {
return build(data,ResultCodeEnum.FAIL);
}
}
3.3 编写统一异常处理类
系统在运行过程中如果出现了异常,默认会直接返回异常信息,比如500错误提示。但是我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要进行统一异常处理。
3.3.1 全局异常处理
package com.atguigu.ssyx.common.exception;
import com.atguigu.ssyx.common.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.fail();
}
}
3.3.2 自定义异常处理
创建自定义异常类
package com.atguigu.ssyx.common.exception;
import com.atguigu.ssyx.common.result.ResultCodeEnum;
import lombok.Data;
@Data
public class SsyxException extends RuntimeException{
//异常状态码
private Integer code;
/**
* 通过状态码和错误消息创建异常对象
* @param message
* @param code
*/
public SsyxException(String message, Integer code) {
super(message);
this.code = code;
}
/**
* 接收枚举类型对象
* @param resultCodeEnum
*/
public SsyxException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
@Override
public String toString() {
return "GuliException{" +
"code=" + code +
", message=" + this.getMessage() +
'}';
}
}
在GlobalExceptionHandler添加方法
/**
* 自定义异常处理方法
* @param e
* @return
*/
@ExceptionHandler(SsyxException.class)
@ResponseBody
public Result error(SsyxException e){
return Result.build(null,e.getCode(), e.getMessage());
}
3.4 编写Swagger配置类
3.4.1 Swagger介绍
前后端分离开发模式中,API文档是最好的沟通方式。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。具有以下几个特点:
1、及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
2、规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
3、一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
4、可测性 (直接在接口文档上进行测试,以方便理解业务)
3.4.2 集成knife4j
文档地址:https://doc.xiaominfo.com/
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
knife4j属于service模块公共资源,因此我们集成到service-uitl模块
3.4.3 添加依赖
common模块添加依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
3.4.4 添加配置类
package com.atguigu.ssyx.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger2配置信息
*/
@Configuration
@EnableSwagger2WebMvc
public class Swagger2Config {
@Bean
public Docket webApiConfig(){
List<Parameter> pars = new ArrayList<>();
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name("userId")
.description("用户token")
//.defaultValue(JwtHelper.createToken(1L, "admin"))
.defaultValue("1")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
pars.add(tokenPar.build());
Docket webApi = new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
//只显示api路径下的页面
.apis(RequestHandlerSelectors.basePackage("com.atguigu.ssyx"))
.paths(PathSelectors.regex("/api/.*"))
.build()
.globalOperationParameters(pars);
return webApi;
}
@Bean
public Docket adminApiConfig(){
List<Parameter> pars = new ArrayList<>();
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name("adminId")
.description("用户token")
.defaultValue("1")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
pars.add(tokenPar.build());
Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
//只显示admin路径下的页面
.apis(RequestHandlerSelectors.basePackage("com.atguigu.ssyx"))
.paths(PathSelectors.regex("/admin/.*"))
.build()
.globalOperationParameters(pars);
return adminApi;
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-API文档")
.description("本文档描述了尚上优选网站微服务接口定义")
.version("1.0")
.contact(new Contact("atguigu", "http://atguigu.com", "atguigu"))
.build();
}
private ApiInfo adminApiInfo(){
return new ApiInfoBuilder()
.title("后台管理系统-API文档")
.description("本文档描述了尚上优选后台系统服务接口定义")
.version("1.0")
.contact(new Contact("atguigu", "http://atguigu.com", "atguigu"))
.build();
}
}
3.5 准备实体类
从资源文件夹中导入实体类到model模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbLmV29g-1686702899978)(images\016.png)]
四 尚上优选平台管理端
1 搭建平台管理端前端环境
1.1 安装相关软件
1.1.1 安装Nodejs
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。简单的说 Node.js 就是运行在服务端的 JavaScript。
官网:https://nodejs.org/en/
中文网:http://nodejs.cn/
LTS:长期支持版本
Current:最新版
本课程采用的是 node-v16.16.0-x64.msi
node -v #如果可以看到版本号,证明安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CG4jsIXB-1686702899978)(images\image-20230327094638056.png)]
如果报以下错误将VS Code以管理员方式运行即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y18h4td2-1686702899979)(images\node%20-v%E6%8A%A5%E9%94%99.bmp?lastModify=1678505294)]
1.1.2 安装vscode
(1)下载地址
https://code.visualstudio.com/
(2)插件安装
为方便后续开发,建议安装如下插件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hgH3FRER-1686702899979)(D:\atguigu\硅谷通用权限系统\01-课件\笔记\images\2.前端基础知识\image-20220225153946727.png)]
(3)创建项目
vscode本身没有新建项目的选项,所以要先创建一个空的文件夹,如project_xxxx。
然后打开vscode,再在vscode里面选择 File -> Open Folder 打开文件夹,这样才可以创建项目。
(4)保存工作区
打开文件夹后,选择“文件 -> 将工作区另存为…”,为工作区文件起一个名字,存储在刚才的文件夹下即可
(5)设置字体大小
左边栏Manage -> settings -> 搜索 “font” -> Font size
1.2 导入前端项目
1.2.1 Vue 脚手架介绍
Vue-cli(俗称:Vue 脚手架)是 vue 官方提供的、快速生成 vue 工程化项目的工具。
特点:① 开箱即用,② 基于 webpack,③ 功能丰富且易于扩展,④ 支持创建 vue2 和 vue3 的项目
(1)创建并运行项目
#全局安装命令行工具
npm install --location=global @vue/cli
#创建一个项目
vue create vue-test #选择vue2
#进入到项目目录
cd vue-test
#启动程序
npm run serve
(2)访问项目
默认8080端口
http://127.0.0.1:8080/
1.2.2 vue-admin-template介绍
(1)vue-element-admin是基于Vue和Element-ui 的一套后台管理系统集成方案。vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。
GitHub地址:https://github.com/PanJiaChen/vue-admin-template
(2)Vue是一套用于构建用户界面的渐进式框架。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
(3)element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建。
1.2.3 复制项目到工作区
从资料中找到平台管理端前端代码 ssyx-admin 复制到工作区
1.2.4 下载项目依赖
进入项目所在目录,使用命令 npm install 下载依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JxCLl83U-1686702899980)(images\image-20230311115353115.png)]
1.2.5 启动前端项目
进入项目所在目录,使用命令 npm run dev 启动项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qfgfwXkP-1686702899980)(images\image-20230311115508663.png)]
1.3 改造登录功能
1.3.1 登录接口
搭建模块,查看2.1章节
创建IndexController
package com.atguigu.ssyx.acl.controller;
import com.atguigu.ssyx.common.result.Result;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/admin/acl/index")
@CrossOrigin //跨域
public class IndexController {
/**
* 1、请求登陆的login
*/
@PostMapping("login")
public Result login() {
Map<String,Object> map = new HashMap<>();
map.put("token","admin-token");
return Result.ok(map);
}
}
1.3.2 获取用户信息接口
/**
* 2 获取用户信息
*/
@GetMapping("info")
public Result info(){
Map<String,Object> map = new HashMap<>();
map.put("name","atguigu");
map.put("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
return Result.ok(map);
}
/**
* 3 退出
*/
@PostMapping("logout")
public Result logout(){
return Result.ok();
}
1.3.3 修改前端整合登录
(1)修改src -> api -> user.js
修改为本地接口路径
import request from '@/utils/request'
export function login(data) {
return request({
url: '/admin/acl/index/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/admin/acl/index/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/admin/acl/index/logout',
method: 'post'
})
}
(2)修改utils -> request.js
修改响应状态码是200
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
// 修改为200 if the custom code is not 20000, it is judged as an error.
if (res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
(3)修改.env.development
修改接口路径为本地
# just a flag
ENV = 'development'
# base api
# VUE_APP_BASE_API = '/dev-api'
# 修改为本地接口地址
VUE_APP_BASE_API = 'http://localhost:8201'
1.4 功能测试
1.4.1 登录功能测试
访问项目:http://localhost:9528/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTeYG2vc-1686702899981)(images\image-20230311142116028.png)]
点击登录,如果可以正常进入页面,表示登录成功
2 权限管理模块-角色管理
2.1 环境搭建
2.1.1 创建service-acl模块
在service模块下创建子模块service-acl
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9z5HIJ5I-1686702899982)(images\017.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RRPp24Rn-1686702899982)(images\018.png)]
2.1.2 创建配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2vd6hE3-1686702899983)(images\019.png)]
(1)application.yml
spring:
application:
name: service-acl
profiles:
active: dev
(2)application-dev.yml
server:
port: 8201
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shequ-acl?characterEncoding=utf-8&useSSL=false
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
2.1.3 创建启动类
package com.atguigu.ssyx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//权限管理模块启动类
@SpringBootApplication
public class ServiceAclApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAclApplication.class, args);
}
}
2.1.4 实体类
已经在model模块提前引入了
实体类说明:
@TableName:表名注解,标识实体类对应的表
@TableId:主键注解,type = IdType.AUTO(数据库 ID 自增)
@TableField:字段注解(非主键)
@TableLogic:逻辑删除
以 Role 角色实体类为例说明:
Role.java
@Data //lombok注解
@ApiModel(description = "角色") //swagger注解提示
@TableName("role") //实体类对应角色表role
public class Role extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "角色名称") //swagger注解提示
@TableField("role_name") //属性对应的表的字段 role_name
private String roleName;
@ApiModelProperty(value = "备注")
@TableField("remark") //属性对应的表的字段 remark
private String remark;
}
BaseEntity.java
@Data
public class BaseEntity implements Serializable {
@ApiModelProperty(value = "id")
@TableId(type = IdType.AUTO) //主键注解,type = IdType.AUTO(数据库 ID 自增)
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@TableLogic //逻辑删除
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false) //exist = false 表示表里面没有对应的字段
private Map<String,Object> param = new HashMap<>();
}
2.1.5 创建数据库
使用资料中的数据库脚本运行创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geyTblwe-1686702899984)(images\image-20230311104732663.png)]