黑马头条(1)
微服务架构开发
- Springboot
- Spring cloud
- Nacos
Nacos 是一个由阿里巴巴开源的服务管理平台,专门用于微服务架构中的服务发现、配置管理和动态 DNS 服务。
环境搭建
目标:接口测试工具及前后端联调
类似今日头条
业务说明
技术栈
综合知识的运用:
- SpringBoot
- SpringCloud
- nacos
- redis
- docker
- elasticSearch
- kafka
- seata
**Seata(Simple Extensible Autonomous Transaction Architecture)**是一个开源的分布式事务解决方案,旨在解决微服务架构中由于跨服务调用而导致的分布式事务问题。
业务或技术解决方案:
- 分布式事务
- 分布式任务调度
- 延迟队列
- 异步线程
- 热数据的处理
- 评论系统
- 关注点赞
软硬编程思想提升
- 业务分析
- 设计
- 编码
- 测试
- 部署
nacos部署
docker拉取镜像
docker pull nacos/nacos-server:1.2.0
docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 nacos/nacos-server:1.2.0
-
MODE=standalone 单机版
-
–restart=always 开机启动
-
-p 8848:8848 映射端口
-
-d 创建一个守护式容器在后台运行
访问地址:http://192.168.200.130:8848/nacos
开发环境说明
项目依赖环境:
- JDK1.8
- Idea
- maven-3.6.1
- Git
全局异常
tinyInt
类型:占1个字节,不指定unsigned(非负数),值范围(-128, 127),指定了unsigned, 值范围(0,255)
tinyInt
通常表示小范围的数值,或者表示true或false,通常值为0表示false,值为1表示true
项目中的持久层使用的是mybatis-plus
,一般都使用mybatis-plus
逆向生成对应的实体类
salt-盐值
手动加密(md5 + 随机字符串)
md5是不可逆加密,md5相同的密码每次加密都一样,不太安全。在md5的基础上手动加盐(salt)处理
引导类
package com.heima.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.heima.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
bootstrap.yml
server:
port: 51801
spring:
application:
name: leadnews-user
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
在nacos中创建配置文件:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.heima.model.user.pojos
logback.xml
logback.xml
是 Logback 日志框架的配置文件,它用于定义日志记录的行为,例如日志输出的格式、日志级别、日志存储的位置等。Logback 是 Java 中常用的日志框架之一,用来替代 Log4j,提供高效、灵活的日志管理。
在 logback.xml
中,你可以配置以下内容:
- 日志级别:指定不同日志消息的严重性等级,如
DEBUG
、INFO
、WARN
、ERROR
。 - 日志输出目的地:你可以设置日志输出到不同的地方,比如控制台(console)、文件(file)或远程服务器(socket)。
- 日志格式:定义日志消息的格式,如时间戳、日志级别、线程信息等。
- 日志记录器(Logger):设置不同类或包的日志记录器,可以为特定的包或类配置不同的日志级别。
- Appender:日志的输出目的地,如
ConsoleAppender
(控制台输出)、FileAppender
(文件输出)等。 - Encoder:控制日志消息的编码格式。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义日志文件的存储地址,使用绝对路径-->
<property name="LOG_HOME" value="e:/logs"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="FILE"/>
</appender>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.boot" level="debug"/>
<root level="info">
<!--<appender-ref ref="ASYNC"/>-->
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
app登录
接口路径 : /api/v1/login/login_auth
请求方式:POST
参数:LoginDto
响应结果:ResponseResult
ApUserLoginController
@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用户登录",tags = "app端用户登录")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@PostMapping("/login_auth")
@ApiOperation("用户登录")
public ResponseResult login(@RequestBody LoginDto dto){
return apUserService.login(dto);
}
}
ApUserMapper
@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}
ApUserService
public interface ApUserService extends IService<ApUser> {
/**
* app端登录功能
* @param dto
* @return
*/
public ResponseResult login(LoginDto dto);
}
ApUserServiceImpl
@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
/**
* app端登录功能
*
* @param dto
* @return
*/
@Override
public ResponseResult login(LoginDto dto) {
//1.正常登录 用户名和密码
if (StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())) {
//1.1 根据手机号查询用户信息
// 用mybatis-plus框架查询
// Wrappers 是 MyBatis-Plus 提供的一个工具类,用于构造查询条件
// getOne() 是 MyBatis-Plus 提供的方法,用于获取单条数据
ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
if (dbUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "用户信息不存在");
}
//1.2 比对密码
String salt = dbUser.getSalt();
String password = dto.getPassword();
String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
if (!pswd.equals(dbUser.getPassword())) {
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
//1.3 返回数据 jwt user
String token = AppJwtUtil.getToken(dbUser.getId().longValue());
Map<String, Object> map = new HashMap<>();
map.put("token", token);
dbUser.setSalt("");
dbUser.setPassword("");
map.put("user", dbUser);
return ResponseResult.okResult(map);
} else {
//2.游客登录
Map<String, Object> map = new HashMap<>();
map.put("token", AppJwtUtil.getToken(0L));
return ResponseResult.okResult(map);
}
}
}
ResponseResult
Postman测试
Swagger测试
前后端分离开发
项目基于前后端分离的架构进行开发,前后端分离架构总体上包括前端和服务端,通常是多人协作开发
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务
主要作用:
- 使得前后端分离开发更加方便,有利于团队协作
- 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担
- 功能测试
引入依赖
只需要在heima-leadnews-common中配置即可,因为其他微服务工程都直接或间接依赖即可。
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
添加配置类
在heima-leadnews-common工程中添加一个配置类
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInfo())
.select()
// 要扫描的API(Controller)基础包
.apis(RequestHandlerSelectors.basePackage("com.heima"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInfo() {
Contact contact = new Contact("黑马程序员","","");
return new ApiInfoBuilder()
.title("黑马头条-平台管理API文档")
.description("黑马头条后台api")
.contact(contact)
.version("1.0.0").build();
}
}
在heima-leadnews-common模块中的resources目录中新增以下目录和文件
文件:resources/META-INF/Spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.exception.ExceptionCatch,\
com.heima.common.swagger.SwaggerConfiguration,\
com.heima.common.swagger.Swagger2Configuration
Swagger常用注解
在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下:
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数的描述信息
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数的描述信息
@ApiImplicitParam属性:
属性 | 取值 | 作用 |
---|---|---|
paramType | 查询参数类型 | |
path | 以地址的形式提交数据 | |
query | 直接跟参数完成自动映射赋值 | |
body | 以流的形式提交 仅支持POST | |
header | 参数在request headers 里边提交 | |
form | 以form表单的形式提交 仅支持POST | |
dataType | 参数的数据类型 只作为标志说明,并没有实际验证 | |
Long | ||
String | ||
name | 接收参数名 | |
value | 接收参数的意义描述 | |
required | 参数是否必填 | |
true | 必填 | |
false | 非必填 | |
defaultValue | 默认值 |
在ApUserLoginController中添加Swagger注解,代码如下:
@RestController
@RequestMapping("/api/v1/login")
@Api(value = "app端用户登录",tags = "app端用户登录")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@PostMapping("/login_auth")
@ApiOperation("用户登录")
public ResponseResult login(@RequestBody LoginDto dto){
return apUserService.login(dto);
}
}
LoginDto
@Data
public class LoginDto {
/**
* 手机号
*/
@ApiModelProperty(value="手机号",required = true)
private String phone;
/**
* 密码
*/
@ApiModelProperty(value="密码",required = true)
private String password;
}
Swagger-ui网址
http://localhost:51801/swagger-ui.html
knife4j测试
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui
app端网关
添加依赖
在heima-leadnews-gateway导入以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
创建对应的网关微服务
在heima-leadnews-gateway下创建heima-leadnews-app-gateway微服务
引导类:
package com.heima.app.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //开启注册中心
public class AppGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(AppGatewayApplication.class,args);
}
}
bootstrap.yml
server:
port: 51601
spring:
application:
name: leadnews-app-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
routes:
# 平台管理
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
认证过滤器
全局过滤器实现jwt校验
在网关微服务中新建全局过滤器:
package com.heima.app.gateway.filter;
import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判断是否是登录
if(request.getURI().getPath().contains("/login")){
//放行
return chain.filter(exchange);
}
//3.获取token
String token = request.getHeaders().getFirst("token");
//4.判断token是否存在
if(StringUtils.isBlank(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.判断token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是过期
int result = AppJwtUtil.verifyToken(claimsBody);
if(result == 1 || result == 2){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
}catch (Exception e){
e.printStackTrace();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//6.放行
return chain.filter(exchange);
}
/**
* 优先级设置 值越小 优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
app前端项目集成
全英文目录放置nginx:
nginx存放目录 : d:develop
配置nginx:
指定前端页面的目录:
heima-leadnews-app.conf
upstream heima-app-gateway{
server localhost:51601;
}
server {
listen 8801;
location / {
root D:/workspace/app-web/;
index index.html;
}
location ~/app/(.*) {
proxy_pass http://heima-app-gateway/$1;
proxy_set_header HOST $host; # 不改变源请求头的值
proxy_pass_request_body on; #开启获取请求体
proxy_pass_request_headers on; #开启获取请求头
proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
}
}
nginx.conf
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 引入自定义配置文件
include leadnews.conf/*.conf;
}
修改配置后需要使用命令重新加载:
nginx -s reload
前端项目地址 : http://localhost:8801