目录
项目介绍
业务说明
技术栈
-
Spring-Cloud-Gateway : 微服务之前架设的网关服务,实现服务注册中的API请求路由,以及控制流速控制和熔断处理都是常用的架构手段,而这些功能Gateway天然支持
-
运用Spring Boot快速开发框架,构建项目工程;并结合Spring Cloud全家桶技术,实现后端个人中心、自媒体、管理中心等微服务。
-
运用Spring Cloud Alibaba Nacos作为项目中的注册中心和配置中心
-
运用mybatis-plus作为持久层提升开发效率
-
运用Kafka完成内部系统消息通知;与客户端系统消息通知;以及实时数据计算
-
运用Redis缓存技术,实现热数据的计算,提升系统性能指标
-
使用Mysql存储用户数据,以保证上层数据查询的高性能
-
使用Mongo存储用户热数据,以保证用户热数据高扩展和高性能指标
-
使用FastDFS作为静态资源存储器,在其上实现热静态资源缓存、淘汰等功能
-
运用Hbase技术,存储系统中的冷数据,保证系统数据的可靠性
-
运用ES搜索技术,对冷数据、文章数据建立索引,以保证冷数据、文章查询性能
-
运用AI技术,来完成系统自动化功能,以提升效率及节省成本。比如实名认证自动化
-
PMD&P3C : 静态代码扫描工具,在项目中扫描项目代码,检查异常点、优化点、代码规范等,为开发团队提供规范统一,提升项目代码质量
环境搭建
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.xxx.xxx:8848/nacos
初始工程搭建
①:项目依赖环境
-
JDK1.8
-
Intellij Idea
-
maven-3.6.1
-
MySQL 8
②:在链接中下载heima-leadnews.zip文件,拷贝到一个没有中文和空格的目录,使用idea打开即可
③:IDEA开发工具配置
④:设置项目编码格式
登录功能实现
需求分析
-
用户点击开始使用
登录后的用户权限较大,可以查看,也可以操作(点赞,关注,评论)
-
用户点击不登录,先看看
游客只有查看的权限
表结构分析
不同模块下与之相关的内容较多,数据库采用模块分库的模式,app端用户相关的内容较多,所以单独设置一个库leadnews_user
表名称 | 说明 |
---|---|
ap_user | APP用户信息表 |
ap_user_fan | APP用户粉丝信息表 |
ap_user_follow | APP用户关注信息表 |
ap_user_realname | APP实名认证信息表 |
在链接中下载sql文件,执行该脚本即可创建对应的数据库,数据表及表中以及准备好的数据
登录需要用到的是ap_user表,表结构如下:
项目中的持久层使用的mybatis-plus,一般都使用mybais-plus逆向生成对应的实体类
app_user表对应的实体类如下:
package com.heima.model.user.pojos;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* APP用户信息表
* @author Dcoin
*/
@Data
@TableName("ap_user")
public class ApUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 密码、通信等加密盐
*/
@TableField("salt")
private String salt;
/**
* 用户名
*/
@TableField("name")
private String name;
/**
* 密码,md5加密
*/
@TableField("password")
private String password;
/**
* 手机号
*/
@TableField("phone")
private String phone;
/**
* 头像
*/
@TableField("image")
private String image;
/**
* 0 男
1 女
2 未知
*/
@TableField("sex")
private Boolean sex;
/**
* 0 未
1 是
*/
@TableField("is_certification")
private Boolean certification;
/**
* 是否身份认证
*/
@TableField("is_identity_authentication")
private Boolean identityAuthentication;
/**
* 0正常
1锁定
*/
@TableField("status")
private Boolean status;
/**
* 0 普通用户
1 自媒体人
2 大V
*/
@TableField("flag")
private Short flag;
/**
* 注册时间
*/
@TableField("created_time")
private Date createdTime;
}
LoginDto
package com.heima.model.user.dtos;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class LoginDto {
/**
* 手机号
*/
@ApiModelProperty(value = "手机号",required = true)
private String phone;
/**
* 密码
*/
@ApiModelProperty(value = "密码",required = true)
private String password;
}
LoginVo
package com.heima.model.user.vo;
import lombok.Data;
import java.util.Date;
@Data
public class LoginVo {
/**
* 主键
*/
private Integer id;
/**
* 用户名
*/
private String name;
/**
* 手机号
*/
private String phone;
/**
* 头像
*/
private String image;
/**
* 0 男
* 1 女
* 2 未知
*/
private Boolean sex;
/**
* 0 未
* 1 是
*/
private Boolean certification;
/**
* 是否身份认证
*/
private Boolean identityAuthentication;
/**
* 0正常
* 1锁定
*/
private Boolean status;
/**
* 0 普通用户
* 1 自媒体人
* 2 大V
*/
private Short flag;
/**
* 注册时间
*/
private Date createdTime;
/**
* token
*/
private String token;
}
实体类对应项目的模块(model主要存放一些与数据库,前端交互的实体类和一些枚举类)
-
dtos包下的实体类用于接收前端请求的数据
-
pojos包下的实体类与数据库的表,字段相对应用于与数据库交互
-
vo包下的实体类用于封装返回的结果与前端进行交互
思路分析
登录流程
1,用户输入了用户名和密码进行登录,校验成功后返回jwt(基于当前用户的id生成)
2,用户游客登录,生成jwt返回(基于默认值0生成)
密码加密
md5是不可逆加密,md5相同的密码每次加密都一样,数据不安全。在md5的基础上手动加盐(salt)处理
注册->生成盐
登录->使用盐来配合验证
接口实现
微服务搭建
在heima-leadnews-service下创建工程heima-leadnews-user
注意:heima-leadnews-user的pom父模块设置为heima-leadnews-service
该项目使用了nacos注册中心,所以将配置文件交给nacos的配置中心统一管理
bootstrap.yml
server:
port: 51801
spring:
application:
name: leadnews-user
cloud:
nacos:
discovery:
server-addr: 192.168.xxx.xxx:8848
config:
server-addr: 192.168.xxx.xxx:8848
file-extension: yml
在nacos中创建配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.xxx.xxx/数据库名?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: 用户名
password: 密码
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.heima.model.user.pojos
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义日志文件的存储地址,使用绝对路径-->
<property name="LOG_HOME" value="d:/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>
功能实现
模块架构
引导类
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);
}
}
controller
package com.heima.user.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.user.service.ApUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/login")
@RequiredArgsConstructor
@Api(value = "app端用户登录",tags = "app端用户登录")
@Slf4j
public class APUserLoginController {
private final ApUserService apUserService;
@PostMapping("/login_auth")
@ApiOperation("用户登录")
public ResponseResult login(@RequestBody LoginDto dto){
log.info("用户登录参数:{}",dto);
return apUserService.login(dto);
}
}
service
ApUserService
package com.heima.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
public interface ApUserService extends IService<ApUser> {
/**
* app端登录功能
* @param dto
* @return
*/
public ResponseResult login(LoginDto dto);
}
ApUserServiceImpl
package com.heima.user.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.user.vo.LoginVo;
import com.heima.user.mapper.ApUserMapper;
import com.heima.user.service.ApUserService;
import com.heima.utils.common.AppJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@Service
//@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
/**
* app端登录功能
*
* @param dto
* @return
*/
@Override
public ResponseResult login(LoginDto dto) {
LoginVo loginVo = new LoginVo();
if (StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())){
//1.正常登录
//1.1根据手机号查询用户信息
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 pwd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
if (!pwd.equals(dbUser.getPassword())){
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
//1.3返回数据 jwt
String token = AppJwtUtil.getToken(dbUser.getId().longValue());
BeanUtils.copyProperties(dbUser,loginVo);
loginVo.setToken(token);
return ResponseResult.okResult(loginVo);
}else {
//2.游客登录
loginVo.setToken(AppJwtUtil.getToken(0L));
return ResponseResult.okResult(loginVo);
}
}
}
mapper
package com.heima.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.user.pojos.ApUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}
接口测试
利用该条数据进行数据测试,初始密码为admin
请求路径:http://localhost:51801/api/v1/login/login_auth
该接口可以正常接收前端的请求并将处理好的数据正确封装返回至前端
网关
配置网关
(1)在heima-leadnews-gateway导入以下依赖
pom文件
<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>
(2)在heima-leadnews-gateway下创建heima-leadnews-app-gateway微服务
注意:heima-leadnews-app-gateway的pom父模块设置为heima-leadnews-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);
}
}
bootstrap.yml
server:
port: 51601
spring:
application:
name: leadnews-app-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.xxx.xxx:8848
config:
server-addr: 192.168.xxx.xxx:8848
file-extension: yml
在nacos的配置中心创建dataid为leadnews-app-gateway的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
环境搭建完成以后,启动项目网关和用户两个服务,使用postman进行测试
请求地址:http://localhost:51601/user/api/v1/login/login_auth
全局过滤器实现网关jwt校验
思路分析
-
用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则拦截器直接放行路由到后台管理微服务进行登录
-
用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
-
用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
-
网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
具体实现
第一:
在认证过滤器中需要用到jwt的解析,需要把工具类AppJwtUtil拷贝一份到网关微服务
第二:
在网关微服务中新建全局过滤器:
package com.heima.app.gateway.filter;
import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
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.util.StringUtils;
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.isEmpty(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;
}
}
注意:
启动user服务,因为目前只有登录功能,所以该过滤器会直接放行,当业务增加的时候,访问其他的接口则会提示需要认证才能访问,这个时候需要在heads中解析正确的token才能正常访问。
前端集成
部署思路
通过nginx来进行配置,功能如下
-
通过nginx的反向代理功能访问后台的网关资源
-
通过nginx的静态服务器功能访问前端静态页面
配置nginx
①:下载链接中的压缩包nginx-1.18.0.zip并解压
②:下载链接中的压缩包app-web.zip并解压
③:配置nginx.conf文件
该项目需要配置三个前端项目,为了简化配置的过程使用了指定配置文件的方式
在nginx安装的conf目录下新建一个文件夹leadnews,在当前文件夹中新建heima-leadnews-app.conf
文件
heima-leadnews-app.conf配置如下:
upstream heima-app-gateway{
server localhost:51601;
}
server {
listen 8801;
location / {
root 前端项目路径;
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 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.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
在nginx安装包中使用命令提示符打开,输入命令nginx启动项目
可查看进程,检查nginx是否启动
重新加载配置文件:nginx -s reload
⑤:打开前端项目进行测试 -- > http://localhost:8801
用浏览器打开,调试移动端模式访问,可对登录接口进行测试
出现该界面说明前端项目部署成功
出现该界面说明前后端联调成功