目录结构

18f63549fbf14a9e866730e78f23454a.png
c5abfc1c456449d1b8c1f2e19efca2b0.png

全局异常

287788fb68b54c9ab0aaf82d728277ba.png

登录手动加密(md5 + 随机字符串)

【问题】md5是不可逆加密,md5相同的密码每次加密都一样,不安全。
【解决】在md5的基础上手动加盐(salt)处理
51135b1974df4aaf92ca2807aa12d794.png
校验过程:
6db7d58b06124215aadbe261f1ad796d.png

接口测试

1. ApiFox中测试

2. swagger

  1. 引入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
  1. 在common模块下添加swagger配置类:
@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();
}
}

03715a928244480fbfe41f21dd9cc950.png

  1. swagger的常用注解
  • @Api:修饰整个类,描述Controller的作用
  • @ApiOperation:描述接口
  • @ApiModelProperty:描述属性

成功后启动项目,浏览器访问:http://localhost:51801/swagger-ui.html

3. knife4j(常用)

knife4j继承了swagger,功能比swagger更强大,也会生成接口文档

  1. 引入依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
  1. 在common中添加配置类:
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {

@Bean(value = "defaultApi2")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//分组名称
.groupName("1.0")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.heima"))
.paths(PathSelectors.any())
.build();
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("黑马头条API文档")
.description("黑马头条API文档")
.version("1.0")
.build();
}
}

同样也需要在resources目录中添加该配置类的全类名
成功后启动项目,浏览器访问:http://host:port/doc.html

网关

1. 配置网关

一个父模块(heima-leadnews-gateway)下,放多个子模块(heima-leadnews-app-gateway)。
506178d045ab4f978a4858eb11993e82.png

  1. 在父模块中引入依赖
<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>
  1. 编写子模块的引导类
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);
}
}
  1. 编写子模块的bootstrap.yml
server:
port: 51601
spring:
application:
name: heima-leadnews-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.140.102:8848
config:
server-addr: 192.168.140.102:8848
file-extension: yml
  1. 在注册中心中引入子模块的配置
    7c829db6c8154f0b9fa1c042543095cc.png
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

2. 设置认证过滤器

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取request、response对象
ServerHttpResponse response = exchange.getResponse();
ServerHttpRequest request = exchange.getRequest();
// 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); // 返回401未授权
return response.setComplete();
}
// 5. 判断token是否有效
try {
if(!AppJwtUtil.isValidToken(token)) {
// token过期
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange); // 放行
}catch (Exception e) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete(); // 抛异常,结束
}
}

/**
* 优先级设置,值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}

认证过滤器用来拦截前端发过来的请求,写在网关里。

文章列表

表的拆分——垂直分表

d70a685c8c9c4d77b89a9e5b8aec994a.png
将一个表的字段分散到多个表,每个表存储其中一部分字段

优势

  • 减少IO争抢,减少锁表的几率(查看文章信息与文章内容互不影响)
  • 充分发挥高频数据的操作效率,对文章信息操作的高频率不会被操作文章详情数据的低效率拖累。

拆分规则

  1. 把不常用的字段单独放在一张表
  2. 把text、blob等大字段拆分出来单独放在一张表
  3. 经常组合查询的字段单独放在一张表

文章详情

方案1. 直接查文章内容表

用户某一条文章,根据id去查询文章内容表,返回渲染页面
e1f300afed754115b635b8742585ed1e.png

方案2. 静态模板展示(效率高)

369f91bc04ab4b13a3024b7cda9063c5.png

把文章的内容变成静态的html文件,把生成之后的文件上传到minio中,并保存生成的html存入文章信息表中(static_url字段),前端渲染的时候,直接获取文章信息表的static_url字段,然后去minIO里获取静态访问页面即可。

Freemaker知识点:Freemarker模板引擎技术

新增文章的测试类:

@Test
public void createStaticUrlTest() throws IOException, TemplateException {
// 1. 获取文章内容
ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, "1302862387124125698L"));
if(apArticleContent != null || StringUtils.isBlank(apArticleContent.getContent())){
// 2. 文章内容通过freemarker生成html文件
Template template = configuration.getTemplate("article.ftl");
Map<String, Object> content = new HashMap<>();
content.put("content", JSONArray.parseArray(apArticleContent.getContent()));
StringWriter out = new StringWriter();
template.process(content, out);
// 3. 把html上传到minio中
InputStream in = new ByteArrayInputStream(out.toString().getBytes());
String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", in);
// 4. 修改ap_article表,保存static_url字段
apArticleService.update(Wrappers.<ApArticle>lambdaUpdate()
.eq(ApArticle::getId, apArticleContent.getArticleId())
.set(ApArticle::getStaticUrl, path));

}
}