自媒体项目
VM CentOS7:数据库密码→Panchunyao123!
开发思维与企业一致
- 三端融合:门户+媒体+运营
- 4g自媒体辉煌时代
- 5g科技互联网风口
- 前后端分离式开发、代码动静分离、保证职
- 能解耦、功能模块互相协调
如何整合分布式中间件到项目中
- 整个流程会做到细致入微
- 帮助迅速提升至少2年以上的项目经验
重点功能技术分析
- Redis:分布式会话、session共享、单点登录、防刷、计数
- Fastdfs+Nginx/OSS/GridFS:搭建分布式文件系统、单文件/批量上传、人脸隐私保护拦截
- Maven:项目构建、聚合、分层、架构设计、面向对象
- 阿里AI:人脸对比、文本/图片自动审核、短信
- SpringCloud:业务分而治之、可伸缩、可扩展、接口服务化
- Freemarker:构建模块页,实现页面静态化
前置技能必备
- Java基础
- 熟悉MySQL/MariaDB
- 掌握Linux的基本命令
课程安排
- 前端构建与运行
- 后端手把手从0到1
- 中间件手把手部署

前后端分离开发模式
传统JavaWeb开发 与 前后端页面交互
运行前端项目
Linux 安装 MySQL【CentOS】_linux 安装mysql-CSDN博客
前端代码在压缩包中 启动D:\apache-tomcat-8.5.93\bin\startup.bat
将里面的imooc-news放到D:\apache-tomcat-8.5.93\webapps中
去浏览器中启动 http://localhost:9090/imooc-news/portal/index.html
SwitchHosts
https://oldj.github.io/SwitchHosts/
在本地把域名和对应的IP給联系绑定起来 [相当于在云服务买了域名后绑定]#imooc-news 127.0.0.1
[这东西需要关闭代理才可以用]127.0.0.1 www.imoocnews.com
127.0.0.1 writer.imoocnews.com
127.0.0.1 admin.imoocnews.com127.0.0.1 article.imoocnews.com
127.0.0.1 user.imoocnews.com
127.0.0.1 files.imoocnews.com
D:\apache-tomcat-8.5.93\webapps\imooc-news\portal\js\app.js
window.app = {
/*
portalIndexUrl: "http://localhost:8080/imooc-news/portal/index.html", // 门户首页地址
writerIndexUrl: "http://localhost:8080/imooc-news/writer/contentMng.html", // 作家中心首页
writerInfoUrl: "http://localhost:8080/imooc-news/writer/accountInfo.html", // 用户信息完善页面
userServerUrl: "http://192.168.1.5:8003", // 用户服务后端接口地址
*/
portalIndexUrl: "http://www.imoocnews.com:9090/imooc-news/portal/index.html", // 门户首页地址
writerLoginUrl: "http://writer.imoocnews.com:9090/imooc-news/writer/passport.html", // 登录页面
writerIndexUrl: "http://writer.imoocnews.com:9090/imooc-news/writer/contentMng.html", // 作家中心首页
writerInfoUrl: "http://writer.imoocnews.com:9090/imooc-news/writer/accountInfo.html", // 用户信息完善页面
adminCenterUrl: "http://admin.imoocnews.com:9090/imooc-news/admin/contentReview.html", // 运营管理平台主页
userServerUrl: "http://user.imoocnews.com:8003", // 用户服务后端接口地址
fsServerUrl: "http://files.imoocnews.com:8004", // 文件服务后端接口地址
adminServerUrl: "http://admin.imoocnews.com:8005", // 运营管理服务后端接口地址
articleServerUrl: "http://article.imoocnews.com:8001", // 文章服务后端接口地址
/**
* 如果本地使用localhost测试可以不使用,如果是ip或者域名测试,cookieDomain改为对应的ip或者域名
* 例:
* ip: 192.168.1.111
* 域名: .imooc.com
*/
cookieDomain: ".imoocnews.com",
......
}
数据库选型与数据导入
- MySql 5.6/5.7
- MariaDB
- Mysql 8.0
表名 注释
admin_user 运营管理平台的admin级别用户
app_user 网站用户
article 文章资讯表
category 新闻资讯文章的分类(或称之为领域)
comments 评论表
fans 粉丝表,用户与粉丝的关联关系,粉丝本质也是用户
构建Maven聚合工程
创建一个 imooc-news-dev 的Maven项目作为一个顶级工程项目
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.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
1.聚合工程可以分为顶级项目(顶级工程,父工程) 与子工程(子modele模块)
这两者的关系其实就是父子继承关系, 子工程在maven中可以称为module,
模块与模块之间是平级的,是可以相互依赖的
2.子模块可以使用顶级工程中所有的资源(依赖), 子模块之间如果有要使用资源的话
必须构建依赖(构建关系)
3.一个顶级工程是可以由多个不同的子工程共同组合而成
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath /> <!--SpringBoot是和后续的SpringCLoud版本联系的-->
</parent>
<properties> <!--属性文件参数 如果mysql是8以上 需要修改mysql的版本号-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mysql-connector-java.version>8.0.33</mysql-connector-java.version>
<mybatis-spring-boot-starter.version>2.1.0</mybatis-spring-boot-starter.version>
<mapper-spring-boot-starter.version>2.1.5</mapper-spring-boot-starter.version>
<pagehelper-spring-boot-starter.version>1.2.12</pagehelper-spring-boot-starter.version>
<okhttp.version>4.2.2</okhttp.version>
<jackson.version>2.10.2</jackson.version>
<commons-codec.version>1.11</commons-codec.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<google-guava.version>28.2-jre</google-guava.version>
<springfox-swagger2.version>2.4.0</springfox-swagger2.version>
<swagger-bootstrap-ui.version>1.6</swagger-bootstrap-ui.version>
<fastdfs.version>1.27.2</fastdfs.version>
<slf4j.version>1.7.21</slf4j.version>
<joda-time.version>2.10.6</joda-time.version>
</properties>
<!--
使用dependencyManagement的目的是为了保证父工程的干净,
也就是说父工程他只负责管理依赖,以及依赖的版本,而不会导入额外的jar依赖。
如此一来父工程的职责就很单一了,而且也符合了面向对象开发的父子继承关系,
依赖的导入只有在各自的子工程中才会进行导入。
-->
<!-- ↓ 管理依赖 不会从外网下载具体jar包 只有在后续子模块配置的时候才会去配置
为了保证父工程的干净,父工程中只负责管理依赖,以及依赖的版本,而不会导入额外的jar依赖
如此一来父工程的职责就很单一了,而且也符合了面向对象开发的父子继承关系
依赖的导入只有在各自的子工程中才会导入
-->
<dependencyManagement>
<dependencies>
<!-- SpringCloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入 mongodb 依赖 -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<!--mongodb-driver.version-->
<version>3.11.1</version>
</dependency>
<!-- mysql 驱动 这样引用方便以后jar包依赖的升级-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- 通用mapper逆向工具 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper-spring-boot-starter.version}</version>
</dependency>
<!--pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot-starter.version}</version>
</dependency>
<!--服务和服务之间的请求-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- apache 工具类 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<!-- google 工具类 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google-guava.version}</version>
</dependency>
<!-- swagger2 配置 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<!-- 文件上传fdfs工具 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${fastdfs.version}</version>
</dependency>
<!-- joda-time 时间工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Java 编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
imooc-news-dev-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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--
imooc-news-dev-common:
通用工程
包含了一些工具类,枚举类,封装的一些公共方法以及一些第三方组件等
-->
<artifactId>imooc-news-dev-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
imooc-news-dev-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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--
imooc-news-dev-model
模型工程, 所有的子工程以及微服务中所涉及到的模型实体类都在此管理
可以包含一些 *pojo,*Bean,*Entity,vo,bo,dto等
-->
<artifactId>imooc-news-dev-model</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 子工程依赖common -->
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
imooc-news-dev-service-api
<?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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!-- imooc-news-dev-service-api
接口工程,集中管理所有的controller中的接口,为了更好的统一管理微服务
-->
<artifactId>imooc-news-dev-service-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
imooc-news-dev-service-user
<?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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>imooc-news-dev-service-user</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
</project>
imooc-news-dev [父]
<?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.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<!-- 这里是子模块[自动创建] 如果物理删除了子工程 这个不会自动删除的要手动删除-->
<module>imooc-news-dev-common</module>
<module>imooc-news-dev-model</module>
<module>imooc-news-dev-service-api</module>
<module>imooc-news-dev-service-user</module>
<module>imooc-news-dev-service-user</module>
</modules>
<!--
1.聚合工程可以分为顶级项目(顶级工程,父工程) 与子工程(子modele模块)
这两者的关系其实就是父子继承关系, 子工程在maven中可以称为module,
模块与模块之间是平级的,是可以相互依赖的
2.子模块可以使用顶级工程中所有的资源(依赖), 子模块之间如果有要使用资源的话
必须构建依赖(构建关系)
3.一个顶级工程是可以由多个不同的子工程共同组合而成
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath /> <!--SpringBoot是和后续的SpringCLoud版本联系的-->
</parent>
<properties> <!--属性文件参数 如果mysql是8以上 需要修改mysql的版本号-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mysql-connector-java.version>8.0.33</mysql-connector-java.version>
<mybatis-spring-boot-starter.version>2.1.0</mybatis-spring-boot-starter.version>
<mapper-spring-boot-starter.version>2.1.5</mapper-spring-boot-starter.version>
<pagehelper-spring-boot-starter.version>1.2.12</pagehelper-spring-boot-starter.version>
<okhttp.version>4.2.2</okhttp.version>
<jackson.version>2.10.2</jackson.version>
<commons-codec.version>1.11</commons-codec.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<google-guava.version>28.2-jre</google-guava.version>
<springfox-swagger2.version>2.4.0</springfox-swagger2.version>
<swagger-bootstrap-ui.version>1.6</swagger-bootstrap-ui.version>
<fastdfs.version>1.27.2</fastdfs.version>
<slf4j.version>1.7.21</slf4j.version>
<joda-time.version>2.10.6</joda-time.version>
</properties>
<!--
使用dependencyManagement的目的是为了保证父工程的干净,
也就是说父工程他只负责管理依赖,以及依赖的版本,而不会导入额外的jar依赖。
如此一来父工程的职责就很单一了,而且也符合了面向对象开发的父子继承关系,
依赖的导入只有在各自的子工程中才会进行导入。
-->
<!-- ↓ 管理依赖 不会从外网下载具体jar包 只有在后续子模块配置的时候才会去配置
为了保证父工程的干净,夫工程中只负责管理依赖,以及依赖的版本,而不会导入额外的jar依赖
如此一来父工程的职责就很单一了,而且也符合了面向对象开发的父子继承关系
依赖的导入只有在各自的子工程中才会导入
-->
<dependencyManagement>
<dependencies>
<!-- SpringCloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 引入 mongodb 依赖 -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<!--mongodb-driver.version-->
<version>3.11.1</version>
</dependency>
<!-- mysql 驱动 这样引用方便以后jar包依赖的升级-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- 通用mapper逆向工具 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper-spring-boot-starter.version}</version>
</dependency>
<!--pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot-starter.version}</version>
</dependency>
<!--服务和服务之间的请求-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- apache 工具类 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<!-- google 工具类 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google-guava.version}</version>
</dependency>
<!-- swagger2 配置 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<!-- 文件上传fdfs工具 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${fastdfs.version}</version>
</dependency>
<!-- joda-time 时间工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Java 编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
com/imooc/user/controller/HelloController.java
package com.imooc.user.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public Object hello(){
return "hello";
}
}
com/imooc/user/Application.java
package com.imooc.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
application.yml
############################################################
#
# 用户微服务
# web访问端口号 约定:8003
#
############################################################
server:
port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: service-user
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
SwitchHosts【一定要先关闭代理 因为它会固定静态ip】
127.0.0.1 user.imoocnews.com
http://user.imoocnews.com:8003/hello
api接口暴露
所有业务下不同的controller都要交给api统一接口去处理,把实现所对应的接口写进API内
把imooc-news-dev-service-user中的pom.xml中的关于SpringBoot的依赖
全部放入imooc-news-dev-service-api中
并且在imooc-news-dev-service-user中写入引用依赖
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
【服务层依赖api层】
更改把user项目的HelloController 复制到dev-service-api 创建一个接口
...api
com/imooc/api/controller/user/HelloControllerApi.java
package com.imooc.api.controller.user;
import org.springframework.web.bind.annotation.GetMapping;
public interface HelloControllerApi {
/**
* api的作用:
* api就相当于企业的领导,老板,部门经理
* 其他的服务层都是实现,他们就相当于员工,只做事情
* 老板(开发人员)来看一下每个人(服务)的进度,做什么事
* 老板不会去问员工,他只会对接部门经理
* 这里所有的api接口就是统一在这里管理和调度的,微服务也如此
*/
/**
* 运作:
* 现在的所有接口都在此暴露,实现都是在各自的微服务中
* 本项目只写项目,不写实现,实现在各自的微服务工程中,因为以业务来划分的微服务有很多
* Controller也会分散在各个微服务工程中,一旦多了就很难统一管理和查看
*
* 其次,微服务之间的调用都是基于接口的
* 如果不这样做,微服务之间的调用就需要互相依赖了
* 耦合对也就很高,接口的目的是为了能够提供解耦
*
* 此外,本项目的接口其实就是一套规范.实现都是由各自的工程去做的处理
* 目前我们使用springboot作为接口的实现的
* 如果未来以后出现新的java web框架,那么我们不需要修改接口
* 只需要去修改对应的实现就可以了,这其实也是解耦的一个体现
*
* Swagger2, 基于接口的自动文档生成
* 所有的配置文件只需要一份,就能再当前项目中去构建了
* 管理起来很方便
*
* 综上所述,这样做法可以提高多服务的项目可扩展性
*/
@GetMapping("/hello")
public Object hello();
}
...user
com/imooc/user/controller/HelloController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
public Object hello(){
return "hello";
}
}
配置logback日志与多环境profile
imooc-news-dev-service-user
先添加一个logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<!-- <property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/service-admin"/>-->
<property name="LOG_HOME" value="C:/Users/Pluminary/Desktop/imooc-news-dev"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/service-user.%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>
<!--<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--</logger>-->
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
com/imooc/user/controller/HelloController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello(){
logger.debug("debug: hello~");
logger.info("info: hello~");
logger.warn("warn: hello~");
logger.error("error: hello~");
return "hello";
}
}
//重新启动后去页面刷新一下
......
29:22.373 [main] INFO com.imooc.user.Application - Started Application in 1.061 seconds (JVM running for 1.543)
29:40.693 [http-nio-8003-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
29:40.694 [http-nio-8003-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
29:40.696 [http-nio-8003-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 2 ms
29:40.707 [http-nio-8003-exec-1] INFO c.i.user.controller.HelloController - info: hello~
29:40.707 [http-nio-8003-exec-1] WARN c.i.user.controller.HelloController - warn: hello~
29:40.707 [http-nio-8003-exec-1] ERROR c.i.user.controller.HelloController - error: hello~
application.yml
############################################################
#
# 用户微服务
# web访问端口号 约定:8003
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-user
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
application-dev.yml
server:
port: 8003
application-prod.yml
server:
port: 8130
优雅的返回封装结果
com/imooc/user/controller/HelloController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.IMOOCJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello(){
logger.debug("debug: hello~");
logger.info("info: hello~");
logger.warn("warn: hello~");
logger.error("error: hello~");
// return "hello";
// return IMOOCJSONResult.ok();
// return IMOOCJSONResult.ok("hello!");
return IMOOCJSONResult.errorMsg("您的信息有误");
}
}
com/imooc/grace/result/IMOOCJSONResult.java
package com.imooc.grace.result;
/**
*
* @Title: IMOOCJSONResult.java
* @Package com.imooc.utils
* @Description: 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据(json object)后,可自行根据业务去实现相关功能
*
* 200:表示成功
* 500:表示错误,错误信息在msg字段中
* 501:bean验证错误,不管多少个错误都以map形式返回
* 502:拦截器拦截到用户token出错
* 555:异常抛出信息
* 556: 用户qq校验异常
* 557: 校验用户是否在CAS登录,用户门票的校验
* @Copyright: Copyright (c) 2020
* @Company: www.imooc.com
* @author 慕课网 - 风间影月
* @version V1.0
* 这样太麻烦了 直接用枚举类
*/
public class IMOOCJSONResult {
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
private String ok; // 不使用
public static IMOOCJSONResult build(Integer status, String msg, Object data) {
return new IMOOCJSONResult(status, msg, data);
}
public static IMOOCJSONResult build(Integer status, String msg, Object data, String ok) {
return new IMOOCJSONResult(status, msg, data, ok);
}
public static IMOOCJSONResult ok(Object data) {
return new IMOOCJSONResult(data);
}
public static IMOOCJSONResult ok() {
return new IMOOCJSONResult(null);
}
public static IMOOCJSONResult errorMsg(String msg) {
return new IMOOCJSONResult(500, msg, null);
}
public static IMOOCJSONResult errorUserTicket(String msg) {
return new IMOOCJSONResult(557, msg, null);
}
public static IMOOCJSONResult errorMap(Object data) {
return new IMOOCJSONResult(501, "error", data);
}
public static IMOOCJSONResult errorTokenMsg(String msg) {
return new IMOOCJSONResult(502, msg, null);
}
public static IMOOCJSONResult errorException(String msg) {
return new IMOOCJSONResult(555, msg, null);
}
public static IMOOCJSONResult errorUserQQ(String msg) {
return new IMOOCJSONResult(556, msg, null);
}
public IMOOCJSONResult() {
}
public IMOOCJSONResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public IMOOCJSONResult(Integer status, String msg, Object data, String ok) {
this.status = status;
this.msg = msg;
this.data = data;
this.ok = ok;
}
public IMOOCJSONResult(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
} Getter+Setter
这样太麻烦而且观察起来不方便 升级一下变成枚举类 更加优雅!
com/imooc/user/controller/HelloController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.IMOOCJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello(){
logger.debug("debug: hello~");
logger.info("info: hello~");
logger.warn("warn: hello~");
logger.error("error: hello~");
// return "hello";
// return IMOOCJSONResult.ok();
// return IMOOCJSONResult.ok("hello!");
// return IMOOCJSONResult.errorMsg("您的信息有误");
return GraceJSONResult.errorCustom(ResponseStatusEnum.NO_AUTH);
}
}
com/imooc/grace/result/GraceJSONResult.java
package com.imooc.grace.result;
import java.util.Map;
/**
* 自定义响应数据类型枚举升级版本
*
* @Title: IMOOCJSONResult.java
* @Package com.imooc.utils
* @Description: 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据(json object)后,可自行根据业务去实现相关功能
*
* @Copyright: Copyright (c) 2020
* @Company: www.imooc.com
* @author 慕课网 - 风间影月
* @version V2.0
*/
public class GraceJSONResult {
// 响应业务状态码
private Integer status;
// 响应消息
private String msg;
// 是否成功
private Boolean success;
// 响应数据,可以是Object,也可以是List或Map等
private Object data;
/**
* 成功返回,带有数据的,直接往OK方法丢data数据即可
* @param data
* @return
*/
public static GraceJSONResult ok(Object data) {
return new GraceJSONResult(data);
}
/**
* 成功返回,不带有数据的,直接调用ok方法,data无须传入(其实就是null)
* @return
*/
public static GraceJSONResult ok() {
return new GraceJSONResult(ResponseStatusEnum.SUCCESS);
}
public GraceJSONResult(Object data) {
this.status = ResponseStatusEnum.SUCCESS.status();
this.msg = ResponseStatusEnum.SUCCESS.msg();
this.success = ResponseStatusEnum.SUCCESS.success();
this.data = data;
}
/**
* 错误返回,直接调用error方法即可,当然也可以在ResponseStatusEnum中自定义错误后再返回也都可以
* @return
*/
public static GraceJSONResult error() {
return new GraceJSONResult(ResponseStatusEnum.FAILED);
}
/**
* 错误返回,map中包含了多条错误信息,可以用于表单验证,把错误统一的全部返回出去
* @param map
* @return
*/
public static GraceJSONResult errorMap(Map map) {
return new GraceJSONResult(ResponseStatusEnum.FAILED, map);
}
/**
* 错误返回,直接返回错误的消息
* @param msg
* @return
*/
public static GraceJSONResult errorMsg(String msg) {
return new GraceJSONResult(ResponseStatusEnum.FAILED, msg);
}
/**
* 错误返回,token异常,一些通用的可以在这里统一定义
* @return
*/
public static GraceJSONResult errorTicket() {
return new GraceJSONResult(ResponseStatusEnum.TICKET_INVALID);
}
/**
* 自定义错误范围,需要传入一个自定义的枚举,可以到[ResponseStatusEnum.java[中自定义后再传入
* @param responseStatus
* @return
*/
public static GraceJSONResult errorCustom(ResponseStatusEnum responseStatus) {
return new GraceJSONResult(responseStatus);
}
public static GraceJSONResult exception(ResponseStatusEnum responseStatus) {
return new GraceJSONResult(responseStatus);
}
public GraceJSONResult(ResponseStatusEnum responseStatus) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
}
public GraceJSONResult(ResponseStatusEnum responseStatus, Object data) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
this.data = data;
}
public GraceJSONResult(ResponseStatusEnum responseStatus, String msg) {
this.status = responseStatus.status();
this.msg = msg;
this.success = responseStatus.success();
}Getter+Setter
}
com/imooc/grace/result/ResponseStatusEnum.java
package com.imooc.grace.result;
/**
* 响应结果枚举,用于提供给GraceJSONResult返回给前端的
* 本枚举类中包含了很多的不同的状态码供使用,可以自定义
* 便于更优雅的对状态码进行管理,一目了然
*/
public enum ResponseStatusEnum {
SUCCESS(200, true, "操作成功!"),
FAILED(500, false, "操作失败!"),
// 50x
UN_LOGIN(501,false,"请登录后再继续操作!"),
TICKET_INVALID(502,false,"会话失效,请重新登录!"),
NO_AUTH(503,false,"您的权限不足,无法继续操作!"),
MOBILE_ERROR(504,false,"短信发送失败,请稍后重试!"),
SMS_NEED_WAIT_ERROR(505,false,"短信发送太快啦~请稍后再试!"),
SMS_CODE_ERROR(506,false,"验证码过期或不匹配,请稍后再试!"),
USER_FROZEN(507,false,"用户已被冻结,请联系管理员!"),
USER_UPDATE_ERROR(508,false,"用户信息更新失败,请联系管理员!"),
USER_INACTIVE_ERROR(509,false,"请前往[账号设置]修改信息激活后再进行后续操作!"),
FILE_UPLOAD_NULL_ERROR(510,false,"文件不能为空,请选择一个文件再上传!"),
FILE_UPLOAD_FAILD(511,false,"文件上传失败!"),
FILE_FORMATTER_FAILD(512,false,"文件图片格式不支持!"),
FILE_MAX_SIZE_ERROR(513,false,"仅支持500kb大小以下的图片上传!"),
FILE_NOT_EXIST_ERROR(514,false,"你所查看的文件不存在!"),
USER_STATUS_ERROR(515,false,"用户状态参数出错!"),
USER_NOT_EXIST_ERROR(516,false,"用户不存在!"),
// 自定义系统级别异常 54x
SYSTEM_INDEX_OUT_OF_BOUNDS(541, false, "系统错误,数组越界!"),
SYSTEM_ARITHMETIC_BY_ZERO(542, false, "系统错误,无法除零!"),
SYSTEM_NULL_POINTER(543, false, "系统错误,空指针!"),
SYSTEM_NUMBER_FORMAT(544, false, "系统错误,数字转换异常!"),
SYSTEM_PARSE(545, false, "系统错误,解析异常!"),
SYSTEM_IO(546, false, "系统错误,IO输入输出异常!"),
SYSTEM_FILE_NOT_FOUND(547, false, "系统错误,文件未找到!"),
SYSTEM_CLASS_CAST(548, false, "系统错误,类型强制转换错误!"),
SYSTEM_PARSER_ERROR(549, false, "系统错误,解析出错!"),
SYSTEM_DATE_PARSER_ERROR(550, false, "系统错误,日期解析出错!"),
// admin 管理系统 56x
ADMIN_USERNAME_NULL_ERROR(561, false, "管理员登录名不能为空!"),
ADMIN_USERNAME_EXIST_ERROR(562, false, "管理员登录名已存在!"),
ADMIN_NAME_NULL_ERROR(563, false, "管理员负责人不能为空!"),
ADMIN_PASSWORD_ERROR(564, false, "密码不能为空后者两次输入不一致!"),
ADMIN_CREATE_ERROR(565, false, "添加管理员失败!"),
ADMIN_PASSWORD_NULL_ERROR(566, false, "密码不能为空!"),
ADMIN_NOT_EXIT_ERROR(567, false, "管理员不存在或密码错误!"),
ADMIN_FACE_NULL_ERROR(568, false, "人脸信息不能为空!"),
ADMIN_FACE_LOGIN_ERROR(569, false, "人脸识别失败,请重试!"),
CATEGORY_EXIST_ERROR(570, false, "文章分类已存在,请换一个分类名!"),
// 媒体中心 相关错误 58x
ARTICLE_COVER_NOT_EXIST_ERROR(580, false, "文章封面不存在,请选择一个!"),
ARTICLE_CATEGORY_NOT_EXIST_ERROR(581, false, "请选择正确的文章领域!"),
ARTICLE_CREATE_ERROR(582, false, "创建文章失败,请重试或联系管理员!"),
ARTICLE_QUERY_PARAMS_ERROR(583, false, "文章列表查询参数错误!"),
ARTICLE_DELETE_ERROR(584, false, "文章删除失败!"),
ARTICLE_WITHDRAW_ERROR(585, false, "文章撤回失败!"),
ARTICLE_REVIEW_ERROR(585, false, "文章审核出错!"),
ARTICLE_ALREADY_READ_ERROR(586, false, "文章重复阅读!"),
// 人脸识别错误代码
FACE_VERIFY_TYPE_ERROR(600, false, "人脸比对验证类型不正确!"),
FACE_VERIFY_LOGIN_ERROR(601, false, "人脸登录失败!"),
// 系统错误,未预期的错误 555
SYSTEM_ERROR(555, false, "系统繁忙,请稍后再试!"),
SYSTEM_OPERATION_ERROR(556, false, "操作失败,请重试或联系管理员"),
SYSTEM_RESPONSE_NO_INFO(557, false, "");
// 响应业务状态
private Integer status;
// 调用是否成功
private Boolean success;
// 响应消息,可以为成功或者失败的消息
private String msg;
ResponseStatusEnum(Integer status, Boolean success, String msg) {
this.status = status;
this.success = success;
this.msg = msg;
}
public Integer status() {
return status;
}
public Boolean success() {
return success;
}
public String msg() {
return msg;
}
}
{
"status": 200,
"msg": "操作成功!",
"success": true,
"data": null
}
配置数据库逆向生成实体类
引入mybatis-generator-database[新建工程项目]
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.imooc</groupId>
<artifactId>mybatis-generator-database</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入log4j日志依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<!-- 阿里开源数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!--mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!-- mybatis 逆向生成工具 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
整合Mybatis
dependencymanagement 无法引入包依赖-CSDN博客
关于Maven依赖dependency无法引入的问题_
Maven配置仓库、阿里云镜像、环境变量(史上最全最详细)_maven配置阿里云镜像-CSDN博客
将mybatis-generator-database中的pojo[AppUser+Fans]复制转移到imooc-news-dev-model com/imooc/pojo中
将mybatis-generator-database中的com/imooc/user/mapper[AppUserMapper、FansMapper]复制转移到imooc-news-service-user com/imooc/user/mapper中
所有的服务都是要实现API的
将mybatis-generator-database中的com/imooc/my/mapper[MyMapper]复制转移到imooc-news-dev-service-api com/imooc/my/mapper中
将..database中的resources的mapper[AppUserMapper.xml、FansMapper.xml]转移到...service-user的resources的mapper中
imooc-news-dev-service-user中的resources的application.yml
############################################################
#
# 用户微服务
# web访问端口号 约定:8003
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-user
datasource: # 数据源的相关配置
type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP
driver-class-name: com.mysql.jdbc.Driver # mysql驱动
url: jdbc:mysql://localhost:3306/imooc-news-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: root
hikari:
connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
minimum-idle: 5 # 最小连接数
maximum-pool-size: 20 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
pool-name: DateSourceHikariCP # 连接池名字
max-lifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
connection-test-query: SELECT 1
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
############################################################
#
# mybatis 配置
#
############################################################
mybatis:
type-aliases-package: com.imooc.pojo # 所有POJO类所在包路径
mapper-locations: classpath:mapper/*.xml # mapper映射文件
############################################################
#
# mybatis mapper 配置
#
############################################################
# 通用 Mapper 配置
mapper:
mappers: com.imooc.my.mapper.MyMapper
not-empty: false # 在进行数据库操作的的时候,判断表达式 username != null, 是否追加 username != ''
identity: MYSQL
# 分页插件配置
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
【解决】SLF4J: Class path contains multiple SLF4J bindings._启动metastore时slf4j: class path contains multiple sl-CSDN博客
整合MongoDB踩坑记录及解决方法 - 会飞的猪仔 - 博客园 (cnblogs.com)
com/imooc/user/Application.java
package com.imooc.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.user.mapper")
@ComponentScan("com.imooc")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Swagger2接口文檔工具的使用
imooc-news-dev-service-api
<!-- swagger2 配置 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
com/imooc/api/config/Swagger2.java
package com.imooc.api.config;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration //Springboot啓動的時候會被掃描到并且加載
@EnableSwagger2
public class Swagger2 {
// http://localhost:8088/swagger-ui.html 原路径
//http://user.imoocnews.com:8003/swagger-ui.htm
// http://localhost:8088/doc.html 新路径
//http://user.imoocnews.com:8003/doc.html
// 配置swagger2核心配置 docket
@Bean
public Docket createRestApi() {
Predicate<RequestHandler> adminPredicate = RequestHandlerSelectors.basePackage("com.imooc.admin.controller");
// Predicate<RequestHandler> articlePredicate = RequestHandlerSelectors.basePackage("com.imooc.article.controller");
Predicate<RequestHandler> userPredicate = RequestHandlerSelectors.basePackage("com.imooc.user.controller");
Predicate<RequestHandler> filesPredicate = RequestHandlerSelectors.basePackage("com.imooc.files.controller");
return new Docket(DocumentationType.SWAGGER_2) // 指定api类型为swagger2
.apiInfo(apiInfo()) // 用于定义api文档汇总信息
.select()
.apis(Predicates.or(userPredicate, adminPredicate, filesPredicate))
// .apis(Predicates.or(adminPredicate, articlePredicate, userPredicate, filesPredicate))
.paths(PathSelectors.any()) // 所有controller
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("慕课新闻·自媒体接口api") // 文档页标题
.contact(new Contact("imooc",
"https://www.imooc.com",
"abc@imooc.com")) // 联系人信息
.description("专为慕课新闻·自媒体平台提供的api文档") // 详细信息
.version("1.0.1") // 文档版本号
.termsOfServiceUrl("https://www.imooc.com") // 网站地址
.build();
}
}
com/imooc/api/controller/user/HelloControllerApi.java
package com.imooc.api.controller.user;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
@Api(value = "controller的標題",tags = {"xx功能的Controller"})
public interface HelloControllerApi {
@ApiOperation(value = "hello方法的接口",notes = "hello方法的接口",httpMethod = "GET")
@GetMapping("/hello")
public Object hello();
}
[maven-之Lifecycle详解_maven lifecycle-CSDN博客](https://blog.csdn.net/qq_39505065/article/details/102915403#第三部分 PS)
Maven中的Lifecycle的install是什麽 ?
将包安装到本地存储库中,作为本地其他项目的依赖项
梳理短信登錄注冊流程
- 短信登录注册
- 短信验证码发送与限制
- 分布式会话
- 用户信息完善,OSS/FastDFS文件上传
- AOP日志监控
aliyun.properties
#这里需要去阿里云上购买短信验证 [电脑aliyun_Key.txt有]
aliyun.accessKeyID=
aliyun.accessKeySecret=
SendSms_短信服务_API调试-阿里云OpenAPI开发者门户 (aliyun.com)
短信发送并查询示例_短信服务_示例中心-阿里云OpenAPI开发者门户 (aliyun.com)
RAM 访问控制 (aliyun.com)
短信服务 (aliyun.com)
云服务器管理控制台 (aliyun.com)
【imooc-news-dev-common】
pom.xml
加入springboot依赖包
<?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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--
imooc-news-dev-common:
通用工程
包含了一些工具类,枚举类,封装的一些公共方法以及一些第三方组件等
-->
<artifactId>imooc-news-dev-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- 第三方云厂商相关依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
</project>
com/imooc/utils/extend/AliyunResource.java
package com.imooc.utils.extend;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:aliyun.properties")
@ConfigurationProperties(prefix = "aliyun") //这里是前缀
public class AliyunResource {
private String accessKeyID;
private String accessKeySecret;
public String getAccessKeyID() {
return accessKeyID;
}
public void setAccessKeyID(String accessKeyID) {
this.accessKeyID = accessKeyID;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
}
com/imooc/utils/SMSUtils.java
package com.imooc.utils;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.imooc.utils.extend.AliyunResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component //工具类 可以作为组件
public class SMSUtils {
@Autowired
public AliyunResource aliyunResource;
final static Logger logger = LoggerFactory.getLogger(SMSUtils.class);
public void sendSMS(String mobile, String code) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou",
aliyunResource.getAccessKeyID(),
aliyunResource.getAccessKeySecret());
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.putQueryParameter("RegionId", "cn-hangzhou");
//给对方发送的手机号
request.putQueryParameter("PhoneNumbers", mobile);
request.putQueryParameter("SignName", "小潘科技");//控制台可以添加签名
request.putQueryParameter("TemplateCode", "SMS_467115116");
request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");//JSON对象字符串
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
// 打印阿里云API的响应结果
logger.info("Aliyun SMS API response: " + response.getData());
} catch (ServerException e) {
e.printStackTrace();
logger.error("ServerException: " + e.getMessage());
} catch (ClientException e) {
e.printStackTrace();
logger.error("ClientException: " + e.getMessage());
}
}
}
com/imooc/api/controller/user/PassportControllerApi.java[接口]
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode();
}
com/imooc/user/controller/PassportController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.PassportControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.utils.SMSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("passport")
public class PassportController implements PassportControllerApi {
final static Logger logger = LoggerFactory.getLogger(PassportController.class);
@Autowired
private SMSUtils smsUtils;
// 这里去除的原因是因为新建了一个BaseController 在里面有信息 且在这加个extends
// @Autowired
// private RedisOperator redis;
@Override
public GraceJSONResult getSMSCode(){
// 生成6位随机验证码
String random = String.valueOf((int)((Math.random() * 9 + 1) * 100000));
// 打印生成的验证码以便调试
logger.info("Generated SMS code: " + random);
// String random = ((Math.random() * 9 + 1) * 100000) + "";
smsUtils.sendSMS("15027597319",random);//可以用MyInfo.getMobile代替
// 记录发送短信的结果(添加日志)
logger.info("SMS sent to 15027597000 with code: " + random);
return GraceJSONResult.ok();
}
}
com/imooc/grace/result/GraceJSONResult.java
package com.imooc.grace.result;
import java.util.Map;
/**
* 自定义响应数据类型枚举升级版本
*
* @Title: IMOOCJSONResult.java
* @Package com.imooc.utils
* @Description: 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据(json object)后,可自行根据业务去实现相关功能
*
* @Copyright: Copyright (c) 2020
* @Company: www.imooc.com
* @author 慕课网 - 风间影月
* @version V2.0
*/
public class GraceJSONResult {
// 响应业务状态码
private Integer status;
// 响应消息
private String msg;
// 是否成功
private Boolean success;
// 响应数据,可以是Object,也可以是List或Map等
private Object data;
/**
* 成功返回,带有数据的,直接往OK方法丢data数据即可
* @param data
* @return
*/
public static GraceJSONResult ok(Object data) {
return new GraceJSONResult(data);
}
/**
* 成功返回,不带有数据的,直接调用ok方法,data无须传入(其实就是null)
* @return
*/
public static GraceJSONResult ok() {
return new GraceJSONResult(ResponseStatusEnum.SUCCESS);
}
public GraceJSONResult(Object data) {
this.status = ResponseStatusEnum.SUCCESS.status();
this.msg = ResponseStatusEnum.SUCCESS.msg();
this.success = ResponseStatusEnum.SUCCESS.success();
this.data = data;
}
/**
* 错误返回,直接调用error方法即可,当然也可以在ResponseStatusEnum中自定义错误后再返回也都可以
* @return
*/
public static GraceJSONResult error() {
return new GraceJSONResult(ResponseStatusEnum.FAILED);
}
/**
* 错误返回,map中包含了多条错误信息,可以用于表单验证,把错误统一的全部返回出去
* @param map
* @return
*/
public static GraceJSONResult errorMap(Map map) {
return new GraceJSONResult(ResponseStatusEnum.FAILED, map);
}
/**
* 错误返回,直接返回错误的消息
* @param msg
* @return
*/
public static GraceJSONResult errorMsg(String msg) {
return new GraceJSONResult(ResponseStatusEnum.FAILED, msg);
}
/**
* 错误返回,token异常,一些通用的可以在这里统一定义
* @return
*/
public static GraceJSONResult errorTicket() {
return new GraceJSONResult(ResponseStatusEnum.TICKET_INVALID);
}
/**
* 自定义错误范围,需要传入一个自定义的枚举,可以到[ResponseStatusEnum.java[中自定义后再传入
* @param responseStatus
* @return
*/
public static GraceJSONResult errorCustom(ResponseStatusEnum responseStatus) {
return new GraceJSONResult(responseStatus);
}
public static GraceJSONResult exception(ResponseStatusEnum responseStatus) {
return new GraceJSONResult(responseStatus);
}
public GraceJSONResult(ResponseStatusEnum responseStatus) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
}
public GraceJSONResult(ResponseStatusEnum responseStatus, Object data) {
this.status = responseStatus.status();
this.msg = responseStatus.msg();
this.success = responseStatus.success();
this.data = data;
}
public GraceJSONResult(ResponseStatusEnum responseStatus, String msg) {
this.status = responseStatus.status();
this.msg = msg;
this.success = responseStatus.success();
}
public GraceJSONResult() {
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
安装配置整合Redis
通过Xftp7把redis-5.0.7.tar.gz传入到服务器
[root@iZbp1dssknxftmjczbtpndZ ~]# tar -zxvf redis-5.0.7.tar.gz
[root@iZbp1dssknxftmjczbtpndZ ~]# ls
apache-zookeeper-3.6.0-bin rabbitmq-server-3.8.2-1.el7.noarch.rpm
apache-zookeeper-3.6.0-bin.tar.gz redis-5.0.7
erlang-22.3-1.el7.x86_64.rpm redis-5.0.7.tar.gz
[root@iZbp1dssknxftmjczbtpndZ ~]# cd redis-5.0.7
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# ll
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# yum install gcc-c++
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# make
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# cd /usr/local/ ★
[root@iZbp1dssknxftmjczbtpndZ local]# ll
[root@iZbp1dssknxftmjczbtpndZ local]# pwd
/usr/local
[root@iZbp1dssknxftmjczbtpndZ local]# cd redis/ ★
[root@iZbp1dssknxftmjczbtpndZ redis]# ll
total 4
drwxr-xr-x 2 root root 4096 May 15 11:19 bin
[root@iZbp1dssknxftmjczbtpndZ redis]# cd bin ★
[root@iZbp1dssknxftmjczbtpndZ bin]# ll
total 32772
-rwxr-xr-x 1 root root 4366880 May 15 11:04 redis-benchmark
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-check-aof
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-check-rdb
-rwxr-xr-x 1 root root 4807952 May 15 11:04 redis-cli
-rw-r--r-- 1 root root 0 May 15 11:19 redis.conf
lrwxrwxrwx 1 root root 12 May 15 11:04 redis-sentinel -> redis-server
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-server
[root@iZbp1dssknxftmjczbtpndZ redis]# cd
[root@iZbp1dssknxftmjczbtpndZ ~]# cd redis-5.0.7
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# ls
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# cp redis.conf /usr/local/redis/bin/
cp: overwrite ‘/usr/local/redis/bin/redis.conf’? y
[root@iZbp1dssknxftmjczbtpndZ redis-5.0.7]# cd /usr/local/redis/bin/
[root@iZbp1dssknxftmjczbtpndZ bin]# ll
total 32836
-rwxr-xr-x 1 root root 4366880 May 15 11:04 redis-benchmark
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-check-aof
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-check-rdb
-rwxr-xr-x 1 root root 4807952 May 15 11:04 redis-cli
-rw-r--r-- 1 root root 61797 May 15 11:28 redis.conf
lrwxrwxrwx 1 root root 12 May 15 11:04 redis-sentinel -> redis-server
-rwxr-xr-x 1 root root 8125288 May 15 11:04 redis-server
[root@iZbp1dssknxftmjczbtpndZ bin]# vim redis.conf
在里面 /bind 直接搜索
把bind 127.0.0.1修改成 0.0.0.0在任何地方都可以进行操作修改
在里面 /dae
把daemonize no 改成 daemonize yes[后台启动]
在里面 /require
把requirepass foobared 这里是设置密码 requirepass XXXX
[root@iZbp1dssknxftmjczbtpndZ bin]# ./redis-server redis.conf ★
32421:C 15 May 2024 11:35:36.687 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
32421:C 15 May 2024 11:35:36.687 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=32421, just started
32421:C 15 May 2024 11:35:36.687 # Configuration loaded
[root@iZbp1dssknxftmjczbtpndZ bin]# ps -ef|grep redis
root 32422 1 0 11:35 ? 00:00:00 ./redis-server 0.0.0.0:6379
root 32456 25226 0 11:35 pts/0 00:00:00 grep --color=auto redis
【此时说明已经成功启动Redis】
[root@iZbp1dssknxftmjczbtpndZ bin]# ./redis-cli ★
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name imooc
OK
127.0.0.1:6379> get name
"imooc"
[root@iZbp1dssknxftmjczbtpndZ bin]# ./redis-cli -p 6379 shutdown
★直接进入redis文件内★
[root@iZbp1dssknxftmjczbtpndZ ~]# cd /usr/local/redis/bin //进入文件内
[root@iZbp1dssknxftmjczbtpndZ bin]# ./redis-server redis.conf //启动
[root@iZbp1dssknxftmjczbtpndZ bin]# ./redis-cli //测试
★★
Linux下Redis服务启动与关闭_linux 关闭redis-CSDN博客
安装配置整合Redis-2
下载并安装好 Redis Desktop Manager
新连接设置
名字:redis-imooc-news 47.98.225.105
地址:47.98.225.105:6379
加一下redis的依赖
imooc-news-dev-common
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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--
imooc-news-dev-common:
通用工程
包含了一些工具类,枚举类,封装的一些公共方法以及一些第三方组件等
-->
<artifactId>imooc-news-dev-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 引入 redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- apache 工具类 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- google 工具类 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- joda-time 时间工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<!-- 第三方云厂商相关依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
</project>
com/imooc/utils/RedisOperator.java
package com.imooc.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @Title: Redis 工具类
* @author 风间影月
*/
@Component
public class RedisOperator {
@Resource
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 判断key是否存在
* @param key
* @return
*/
public boolean keyIsExist(String key) {
return redisTemplate.hasKey(key);
}
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:increment key,增加key一次
*
* @param key
* @return
*/
public long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令:decrement key,减少key一次
*
* @param key
* @return
*/
public long decrement(String key, long delta) {
return redisTemplate.opsForValue().decrement(key, delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout
* (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 如果key不存在,则设置,如果存在,则报错
* @param key
* @param value
*/
public void setnx60s(String key, String value) {
redisTemplate.opsForValue().setIfAbsent(key, value, 60, TimeUnit.SECONDS);
}
/**
* 如果key不存在,则设置,如果存在,则报错
* @param key
* @param value
*/
public void setnx(String key, String value) {
redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
/**
* 批量查询,对应mget
* @param keys
* @return
*/
public List<String> mget(List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 批量查询,管道pipeline
* @param keys
* @return
*/
public List<Object> batchGet(List<String> keys) {
// nginx -> keepalive
// redis -> pipeline
List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection src = (StringRedisConnection)connection;
for (String k : keys) {
src.get(k);
}
return null;
}
});
return result;
}
// Hash(哈希表)
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List(列表)
/**
* 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 实现命令:LPOP key,移除并返回列表 key的头元素。
*
* @param key
* @return 列表key的头元素。
*/
public String lpop(String key) {
return (String)redisTemplate.opsForList().leftPop(key);
}
/**
* 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long rpush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
}
Spring Boot集成Redis的坑,踩了! - 知乎 (zhihu.com)
@Autowired和@Resource注解的区别和联系(十分详细,不看后悔)_为什么@resource和@autowired 注入的对象不一样-CSDN博客
妈的有个超级大bug 整我一下午,
@Component public class RedisOperator {
@Autowired private StringRedisTemplate redisTemplate;}
报错信息 Could not autowire. No beans of ‘StringRedisTemplate’ type found.在这里不要本末倒置 回归最原始的报错 那就是pom.xml中的导包依赖问题
有的时候直接复制的项目中的成熟依赖 根据时代的不同可能会导致丢失无法下载依赖
这时要去百度Maven库手动下载 并且手动添加 然后手动导入Project Structure → Libraries 手动导入自己需要的包[记住包的版本 也要在依赖里面体现 < version >],而且如果有红色波浪线的包可以删除 再重新导入即可
Spring里遇到的一个问题,autowired时报找不到bean定义_autowired找不到bean-CSDN博客
完善发送短信接口
com/imooc/api/controller/user/PassportControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request);
}
com/imooc/user/controller/PassportController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.BaseController;
import com.imooc.api.controller.user.PassportControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.utils.RedisOperator;
import com.imooc.utils.SMSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.imooc.utils.IPUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("passport")
public class PassportController extends BaseController implements PassportControllerApi {
final static Logger logger = LoggerFactory.getLogger(PassportController.class);
@Autowired
private SMSUtils smsUtils;
// 这里去除的原因是因为新建了一个BaseController 在里面有信息 且在这加个extends
// @Autowired
// private RedisOperator redis;
@Override
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request){
String userIp = IPUtil.getRequestIp(request);
//根据用户的ip进行限制,限制用户在60秒内只能获得一次验证码
// redis.setnx60s("smscode"+ip);
redis.setnx60s(MOBILE_SMSCODE + ":"+userIp,userIp);
//生成随机验证码并且发送短信
String random = ((Math.random() * 9 + 1) * 100000) + "";
smsUtils.sendSMS("15027597319",random);//可以用MyInfo.getMobile代替
//把验证码存入redis,用于后续进行验证
redis.set(MOBILE_SMSCODE+":"+mobile, random, 30*60);
return GraceJSONResult.ok();
}
}
com/imooc/api/controller/user/BaseController.java
package com.imooc.api.controller.user;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
}
com/imooc/utils/IPUtil.java
package com.imooc.utils;
import javax.servlet.http.HttpServletRequest;
/**
* 用户获得用户ip的工具类
*/
public class IPUtil {
/**
* 获取请求IP:
* 用户的真实IP不能使用request.getRemoteAddr()
* 这是因为可能会使用一些代理软件,这样ip获取就不准确了
* 此外我们如果使用了多级(LVS/Nginx)反向代理的话,ip需要从X-Forwarded-For中获得第一个非unknown的IP才是用户的有效ip。
* @param request
* @return
*/
public static String getRequestIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
联调前端发送短信, 解决跨域问题
http://writer.imoocnews.com:9090/imooc-news/writer/passport.html
因为后台写死了手机号 所以在输入手机号可以随便 点击发送验证码后 会在浏览器控制台输出跨域问题 在后端要設置允許跨域請求
-----------------------------------------------------------------------------------
passport.html:1 Access to XMLHttpRequest at 'http://user.imoocnews.com:8003/passport/getSMSCode?mobile=123334323' from origin 'http://writer.imoocnews.com:9090' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
GET http://user.imoocnews.com:8003/passport/getSMSCode?mobile=123334323 net::ERR_FAILED 200 (OK)
axios.min.js:2 Uncaught (in promise) Error: Network Error
at e.exports (axios.min.js:2:9633)
at l.onerror (axios.min.js:2:8398)
...service-api com/imooc/api/config/CorsConfig.java
package com.imooc.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration //SpringBoot可以加载该信息
public class CorsConfig {
public CorsConfig() {
}
@Bean
public CorsFilter corsFilter() {
// 1. 添加cors配置信息
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
// 设置是否发送cookie信息
config.setAllowCredentials(true);
// 设置允许请求的方式
config.addAllowedMethod("*");
// 设置允许的header
config.addAllowedHeader("*");
// 2. 为url添加映射路径
UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
corsSource.registerCorsConfiguration("/**", config);
// 3. 返回重新定义好的corsSource
return new CorsFilter(corsSource);
}
}
com/imooc/user/controller/PassportController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.PassportControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.SMSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("passport")
public class PassportController extends BaseController implements PassportControllerApi {
final static Logger logger = LoggerFactory.getLogger(PassportController.class);
@Autowired
private SMSUtils smsUtils;
// 这里去除的原因是因为新建了一个BaseController 在里面有信息 且在这加个extends
// @Autowired
// private RedisOperator redis;
@Override
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request){
//获取用户ip
String userIp = IPUtil.getRequestIp(request);
logger.info("User ip:", userIp);
//根据用户的ip进行限制,限制用户在60秒内只能获得一次验证码
redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);
// 生成6位随机验证码
String random = (int)((Math.random() * 9 + 1) * 100000) + "";
// 打印生成的验证码以便调试
// logger.info("Generated SMS code: " + random);
// String random = ((Math.random() * 9 + 1) * 100000) + "";
// smsUtils.sendSMS("15027597319",random);//可以用MyInfo.getMobile代替
// 记录发送短信的结果(添加日志)
// logger.info("SMS sent to 15027597319 with code: " + random);
redis.set(MOBILE_SMSCODE + ":" + mobile, random, 30 * 60);
//记得如果要发送到redis中 则需要先用application-dev.yml导入RedisDesktopManager正确的网络地址127.0.0.1
return GraceJSONResult.ok();
}
}
com/imooc/api/controller/user/PassportControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
@RequestMapping("passport")
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request);
}
拦截并限制60秒用户短信发送
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
import com.imooc.api.interceptors.PassportInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/password/getSMSCode"); //拦截PassportControllerApi里的信息
}
}
com/imooc/api/interceptors/PassportInterceptor.java
package com.imooc.api.interceptors;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PassportInterceptor implements HandlerInterceptor {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
/**
* 拦截请求,访问controller之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获得用户ip
String userIp = IPUtil.getRequestIp(request);
boolean keyIsExist = redis.keyIsExist(MOBILE_SMSCODE + ":" + userIp);
if (keyIsExist) {
GraceException.display(ResponseStatusEnum.SMS_NEED_WAIT_ERROR);
// System.out.println("短信发送频率太大!");
return false;
}
/**
* false:请求被拦截
* true:请求通过验证,放行
*/
return true;
}
/**
* 请求访问到controller之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 请求访问到controller之后,渲染视图之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
dev-common com/imooc/exception/GraceException.java
package com.imooc.exception;
import com.imooc.grace.result.ResponseStatusEnum;
/**
* 优雅的处理异常,统一封装
*/
public class GraceException {
public static void display(ResponseStatusEnum responseStatusEnum) {
throw new MyCustomException(responseStatusEnum);
}
}
com/imooc/exception/MyCustomException.java
package com.imooc.exception;
import com.imooc.grace.result.ResponseStatusEnum;
/**
* 自定义异常
* 目的:统一处理异常信息
* 便于解耦,service与controller错误的解耦,不会被service返回的类型而限制
*/
public class MyCustomException extends RuntimeException {
private ResponseStatusEnum responseStatusEnum;
public MyCustomException(ResponseStatusEnum responseStatusEnum) {
super("异常状态码为:" + responseStatusEnum.status()
+ ";具体异常信息为:" + responseStatusEnum.msg());
this.responseStatusEnum = responseStatusEnum;
}
public ResponseStatusEnum getResponseStatusEnum() {
return responseStatusEnum;
}
public void setResponseStatusEnum(ResponseStatusEnum responseStatusEnum) {
this.responseStatusEnum = responseStatusEnum;
}
}
自定义异常, 返回错误信息
[接上方GraceException、MyCustomException、PassportInterceptor]
dev-common com/imooc/grace/result/ResponseStatusEnum.java
package com.imooc.grace.result;
/**
* 响应结果枚举,用于提供给GraceJSONResult返回给前端的
* 本枚举类中包含了很多的不同的状态码供使用,可以自定义
* 便于更优雅的对状态码进行管理,一目了然
*/
public enum ResponseStatusEnum {
SUCCESS(200, true, "操作成功!"),
FAILED(500, false, "操作失败!"),
// 50x
UN_LOGIN(501,false,"请登录后再继续操作!"),
TICKET_INVALID(502,false,"会话失效,请重新登录!"),
NO_AUTH(503,false,"您的权限不足,无法继续操作!"),
MOBILE_ERROR(504,false,"短信发送失败,请稍后重试!"),
SMS_NEED_WAIT_ERROR(505,false,"短信发送太快啦~请稍后再试!"),
SMS_CODE_ERROR(506,false,"验证码过期或不匹配,请稍后再试!"),
USER_FROZEN(507,false,"用户已被冻结,请联系管理员!"),
USER_UPDATE_ERROR(508,false,"用户信息更新失败,请联系管理员!"),
USER_INACTIVE_ERROR(509,false,"请前往[账号设置]修改信息激活后再进行后续操作!"),
FILE_UPLOAD_NULL_ERROR(510,false,"文件不能为空,请选择一个文件再上传!"),
FILE_UPLOAD_FAILD(511,false,"文件上传失败!"),
FILE_FORMATTER_FAILD(512,false,"文件图片格式不支持!"),
FILE_MAX_SIZE_ERROR(513,false,"仅支持500kb大小以下的图片上传!"),
FILE_NOT_EXIST_ERROR(514,false,"你所查看的文件不存在!"),
USER_STATUS_ERROR(515,false,"用户状态参数出错!"),
USER_NOT_EXIST_ERROR(516,false,"用户不存在!"),
// 自定义系统级别异常 54x
SYSTEM_INDEX_OUT_OF_BOUNDS(541, false, "系统错误,数组越界!"),
SYSTEM_ARITHMETIC_BY_ZERO(542, false, "系统错误,无法除零!"),
SYSTEM_NULL_POINTER(543, false, "系统错误,空指针!"),
SYSTEM_NUMBER_FORMAT(544, false, "系统错误,数字转换异常!"),
SYSTEM_PARSE(545, false, "系统错误,解析异常!"),
SYSTEM_IO(546, false, "系统错误,IO输入输出异常!"),
SYSTEM_FILE_NOT_FOUND(547, false, "系统错误,文件未找到!"),
SYSTEM_CLASS_CAST(548, false, "系统错误,类型强制转换错误!"),
SYSTEM_PARSER_ERROR(549, false, "系统错误,解析出错!"),
SYSTEM_DATE_PARSER_ERROR(550, false, "系统错误,日期解析出错!"),
// admin 管理系统 56x
ADMIN_USERNAME_NULL_ERROR(561, false, "管理员登录名不能为空!"),
ADMIN_USERNAME_EXIST_ERROR(562, false, "管理员登录名已存在!"),
ADMIN_NAME_NULL_ERROR(563, false, "管理员负责人不能为空!"),
ADMIN_PASSWORD_ERROR(564, false, "密码不能为空后者两次输入不一致!"),
ADMIN_CREATE_ERROR(565, false, "添加管理员失败!"),
ADMIN_PASSWORD_NULL_ERROR(566, false, "密码不能为空!"),
ADMIN_NOT_EXIT_ERROR(567, false, "管理员不存在或密码错误!"),
ADMIN_FACE_NULL_ERROR(568, false, "人脸信息不能为空!"),
ADMIN_FACE_LOGIN_ERROR(569, false, "人脸识别失败,请重试!"),
CATEGORY_EXIST_ERROR(570, false, "文章分类已存在,请换一个分类名!"),
// 媒体中心 相关错误 58x
ARTICLE_COVER_NOT_EXIST_ERROR(580, false, "文章封面不存在,请选择一个!"),
ARTICLE_CATEGORY_NOT_EXIST_ERROR(581, false, "请选择正确的文章领域!"),
ARTICLE_CREATE_ERROR(582, false, "创建文章失败,请重试或联系管理员!"),
ARTICLE_QUERY_PARAMS_ERROR(583, false, "文章列表查询参数错误!"),
ARTICLE_DELETE_ERROR(584, false, "文章删除失败!"),
ARTICLE_WITHDRAW_ERROR(585, false, "文章撤回失败!"),
ARTICLE_REVIEW_ERROR(585, false, "文章审核出错!"),
ARTICLE_ALREADY_READ_ERROR(586, false, "文章重复阅读!"),
// 人脸识别错误代码
FACE_VERIFY_TYPE_ERROR(600, false, "人脸比对验证类型不正确!"),
FACE_VERIFY_LOGIN_ERROR(601, false, "人脸登录失败!"),
// 系统错误,未预期的错误 555
SYSTEM_ERROR(555, false, "系统繁忙,请稍后再试!"),
SYSTEM_OPERATION_ERROR(556, false, "操作失败,请重试或联系管理员"),
SYSTEM_RESPONSE_NO_INFO(557, false, "");
// 响应业务状态
private Integer status;
// 调用是否成功
private Boolean success;
// 响应消息,可以为成功或者失败的消息
private String msg;
ResponseStatusEnum(Integer status, Boolean success, String msg) {
this.status = status;
this.success = success;
this.msg = msg;
}
public Integer status() {
return status;
}
public Boolean success() {
return success;
}
public String msg() {
return msg;
}
}
dev-common com/imooc/exception/GraceExceptionHandler.java
package com.imooc.exception;
import com.imooc.grace.result.GraceJSONResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 统一异常拦截处理
* 可以针对异常的类型进行捕获 然后返回json信息到前端
*/
@ControllerAdvice //本质上是实现AOP的管理
public class GraceExceptionHandler {
@ExceptionHandler(MyCustomException.class)
//只要是这个类的异常都会进入下面的方法
@ResponseBody
public GraceJSONResult returnMyException(MyCustomException e){
e.printStackTrace(); //打印信息
return GraceJSONResult.exception(e.getResponseStatusEnum());
}
}
验证BO信息(注册登录接口)
service-api com/imooc/api/controller/user/PassportControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; //用户需求验证
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
@RequestMapping("passport")
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request);
@ApiOperation(value = "一键注册登录接口",notes = "一键注册登录接口",httpMethod = "POST")
@PostMapping("/doLogin") //表单里面用post RequestBody后面传过来的东西和json对象对应
public GraceJSONResult doLogin(@RequestBody @Valid RegistLoginBO registLoginBO, BindingResult result);
}
dev-model com/imooc/pojo/bo/RegistLoginBO.java
package com.imooc.pojo.bo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
//加上@data 会自动生成getter+setter
public class RegistLoginBO {
//不为空 空的话可以返回 不用NOTNULL因为无法校验空字符串 用NotBlank
@NotBlank(message = "手机号不能为空")
private String mobile;
@NotBlank(message = "短信验证码不能为空")
private String smsCode;
@Override
public String toString() {
return "RegistLoginBO{" +
"mobile='" + mobile + '\'' +
", smsCode='" + smsCode + '\'' +
'}';
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getSmsCode() {
return smsCode;
}
public void setSmsCode(String smsCode) {
this.smsCode = smsCode;
}
}
service-user com/imooc/user/controller/PassportController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.PassportControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.utils.IPUtil;
import com.imooc.utils.SMSUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("passport")
public class PassportController extends BaseController implements PassportControllerApi {
final static Logger logger = LoggerFactory.getLogger(PassportController.class);
@Autowired
private SMSUtils smsUtils;
// 这里去除的原因是因为新建了一个BaseController 在里面有信息 且在这加个extends
// @Autowired
// private RedisOperator redis;
@Override
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request){
//获取用户ip
String userIp = IPUtil.getRequestIp(request);
logger.info("User ip:", userIp);
//根据用户的ip进行限制,限制用户在60秒内只能获得一次验证码
redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);
// 生成6位随机验证码
String random = (int)((Math.random() * 9 + 1) * 100000) + "";
// 打印生成的验证码以便调试
// logger.info("Generated SMS code: " + random);
// String random = ((Math.random() * 9 + 1) * 100000) + "";
// smsUtils.sendSMS("15027597319",random);//可以用MyInfo.getMobile代替
// 记录发送短信的结果(添加日志)
// logger.info("SMS sent to 15027597319 with code: " + random);
redis.set(MOBILE_SMSCODE + ":" + mobile, random, 30 * 60);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult doLogin(RegistLoginBO registLoginBO, BindingResult result) {
//0.判断BindingResult中是否保存了错误的验证信息 如果有则需要返回
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
String mobile = registLoginBO.getMobile();
String smsCode = registLoginBO.getSmsCode();
//1.校验验证码是否匹配[在redis中去获取]
String redisSMSCode = redis.get(MOBILE_SMSCODE + ":" + mobile); //为空||不同值
if (StringUtils.isBlank(redisSMSCode) || !redisSMSCode.equalsIgnoreCase(smsCode)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
return GraceJSONResult.ok();
}
}
service-api com/imooc/api/BaseController.java
package com.imooc.api;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
/**可以公用 就放到BaseController里面
* 在任何controller中都可以调用和使用
* 获取BO中的错误信息
*
* @param result
* @return
*/
public Map<String, String> getErrors(BindingResult result){
//对应着RegistLoginBO的信息
Map<String, String> map = new HashMap<>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList){
//发生验证错误所对应的某个属性
String field = error.getField();
//验证的错误信息
String msg = error.getDefaultMessage();
map.put(field, msg);
}
return map;
}
}
http://writer.imoocnews.com:8003/doc.html 打开校验
POST:/passport/doLogin
{
"mobile":"",
"smsCode":""
}
{
"status": 500,
"msg": "操作失败!",
"success": false,
-"data": {
"smsCode": "短信验证码不能为空",
"mobile": "手机号不能为空"
}
}
--------------------------------------------------------
//不为空 空的话可以返回 不用NOTNULL因为无法校验空字符串 用NotBlank
@NotBlank(message = "手机号不能为空")
private String mobile;
@NotBlank(message = "短信验证码不能为空")
private String smsCode;
// 要注意上面的为NotBlank 不然它验证的结果会跳过手机号判断 直接说验证码错误
// 因为NotNull在处理"mobile":"", "smsCode":""的时候空字符串也算入不为空
//NotBlank兼顾NotNull
通过数据库 查询老用户_老用户添加
service-api com/imooc/api/controller/user/PassportControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; //用户需求验证
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
@RequestMapping("passport")
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request);
@ApiOperation(value = "一键注册登录接口",notes = "一键注册登录接口",httpMethod = "POST")
@PostMapping("/doLogin") //表单里面用post RequestBody后面传过来的东西和json对象对应
public GraceJSONResult doLogin(@RequestBody @Valid RegistLoginBO registLoginBO, BindingResult result);
}
service-user com/imooc/user/controller/PassportController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.PassportControllerApi;
import com.imooc.enums.UserStatus;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.user.service.impl.UserService;
import com.imooc.utils.IPUtil;
import com.imooc.utils.SMSUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("passport")
public class PassportController extends BaseController implements PassportControllerApi {
final static Logger logger = LoggerFactory.getLogger(PassportController.class);
@Autowired
private SMSUtils smsUtils;
@Autowired
private UserService userService;
// 这里去除的原因是因为新建了一个BaseController 在里面有信息 且在这加个extends
// @Autowired
// private RedisOperator redis;
@Override
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request){
//获取用户ip
String userIp = IPUtil.getRequestIp(request);
logger.info("User ip:", userIp);
//根据用户的ip进行限制,限制用户在60秒内只能获得一次验证码
redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);
// 生成6位随机验证码
String random = (int)((Math.random() * 9 + 1) * 100000) + "";
// 打印生成的验证码以便调试
// logger.info("Generated SMS code: " + random);
// String random = ((Math.random() * 9 + 1) * 100000) + "";
// smsUtils.sendSMS("15027597319",random);//可以用MyInfo.getMobile代替
// 记录发送短信的结果(添加日志)
// logger.info("SMS sent to 15027597319 with code: " + random);
redis.set(MOBILE_SMSCODE + ":" + mobile, random, 30 * 60);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult doLogin(RegistLoginBO registLoginBO, BindingResult result) {
//0.判断BindingResult中是否保存了错误的验证信息 如果有则需要返回
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
String mobile = registLoginBO.getMobile();
String smsCode = registLoginBO.getSmsCode();
//1.校验验证码是否匹配[在redis中去获取]
String redisSMSCode = redis.get(MOBILE_SMSCODE + ":" + mobile); //为空||不同值
if (StringUtils.isBlank(redisSMSCode) || !redisSMSCode.equalsIgnoreCase(smsCode)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
//2.查询数据库,判断该用户注册
AppUser user = userService.queryMobileIsExist(mobile);
if (user != null && user.getActiveStatus() == UserStatus.FROZEN.type){
//如果用户不为空,并且状态为冻结,则直接抛出异常,禁止登录
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_FROZEN);
}else if (user == null){
//如果用户没有注册过,则为null,需要注册信息入库
user = userService.createUser(mobile);
}
return GraceJSONResult.ok(user);
}
}
service-user com/imooc/user/service/impl/UserService.java[接口]
package com.imooc.user.service.impl;
import com.imooc.pojo.AppUser;
public interface UserService {
/**
* 判断用户是否存在,如果存在返回user信息
*/
public AppUser queryMobileIsExist(String mobile);
/**
* 创建用户,新增用户记录到数据库
*/
public AppUser createUser(String mobile);
}
service-user com/imooc/user/service/UserServiceimpl.java
package com.imooc.user.service;
import com.imooc.enums.Sex;
import com.imooc.enums.UserStatus;
import com.imooc.pojo.AppUser;
import com.imooc.user.mapper.AppUserMapper;
import com.imooc.user.service.impl.UserService;
import com.imooc.utils.DesensitizationUtil;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import com.imooc.utils.DateUtil;
import java.util.Date;
@Service
public class UserServiceimpl implements UserService {
@Autowired
public AppUserMapper appUserMapper; //基本的CRUD都可以
@Autowired
public Sid sid;
private static final String USER_FACE0 = "https://raw.githubusercontent.com/P-luminary/images/10d94134b65e13cc8ec9b8a9aeae4f958921cab7/data/Imooc_Cat.jpg";
private static final String USER_FACE1 = "https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg";
@Override
public AppUser queryMobileIsExist(String mobile) {
Example userExample = new Example(AppUser.class);
Example.Criteria userCriteria = userExample.createCriteria();
userCriteria.andEqualTo("mobile", mobile);
AppUser user = appUserMapper.selectOneByExample(userExample);
return null;
}
@Transactional //对整个类的方法,事务起作用。无异常时正常提交,有异常时数据回滚
@Override
public AppUser createUser(String mobile) {
/**
* 互联网项目都要考虑可扩展性
* 如果未来的业务激增,那么就需要分表分库
* 那么数据库表主键id必须保证全局(全库)唯一,不得重复
*/
String userId = sid.nextShort();
AppUser user = new AppUser();
user.setId(userId);
user.setMobile(mobile);
user.setNickname("用户:" + DesensitizationUtil.commonDisplay(mobile)); //給手机号加** 是脱敏操作
user.setFace(USER_FACE1);
user.setBirthday(DateUtil.stringToDate("2024-06-29")); //字符串转换Date类型
user.setSex(Sex.secret.type);
user.setActiveStatus(UserStatus.INACTIVE.type);//是否激活
user.setTotalIncome(0);//收入
user.setCreatedTime(new Date());
user.setUpdatedTime(new Date());
return user;
}
}
dev-model com/imooc/pojo/AppUser.java
package com.imooc.pojo;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Table(name = "app_user")
public class AppUser {
@Id
private String id;
/**
* 手机号
*/
private String mobile;
/**
* 昵称,媒体号
*/
private String nickname;
/**
* 头像
*/
private String face;
/**
* 真实姓名
*/
private String realname;
/**
* 邮箱地址
*/
private String email;
/**
* 性别 1:男 0:女 2:保密
*/
private Integer sex;
/**
* 生日
*/
private Date birthday;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区县
*/
private String district;
/**
* 用户状态:0:未激活。 1:已激活:基本信息是否完善,真实姓名,邮箱地址,性别,生日,住址等,如果没有完善,则用户不能发表评论,不能点赞,不能关注。2:已冻结。
*/
@Column(name = "active_status")
private Integer activeStatus;
/**
* 累计已结算的收入金额,也就是已经打款的金额,每次打款后再此累加
*/
@Column(name = "total_income")
private Integer totalIncome;
/**
* 创建时间 创建时间
*/
@Column(name = "created_time")
private Date createdTime;
/**
* 更新时间 更新时间
*/
@Column(name = "updated_time")
private Date updatedTime;
}Getter + Setter
service-user com/imooc/user/Application.java
package com.imooc.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.user.mapper")
@ComponentScan(basePackages = {"com.imooc","com.imooc.utils"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
【来自Chatgpt】
java.sql.SQLException: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'READ' at line 1
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readErrorPacket(AbstractQueryProtocol.java:1594)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readPacket(AbstractQueryProtocol.java:1453)
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.getResult(AbstractQueryProtocol.java:1415)
这个新的错误信息表明在设置 tx_isolation 时,语法存在问题。我们需要确保设置事务隔离级别的语法是正确的。以下是一些更具体的步骤来解决这个问题:
1. 确认MariaDB版本支持的语法
在MariaDB中,设置事务隔离级别的正确语法可能与MySQL略有不同。确保使用MariaDB支持的正确语法。
2. 确认正确的事务隔离级别语法
根据MariaDB的官方文档,设置事务隔离级别的正确方法是使用以下语法:
sql
复制代码
SET SESSION tx_isolation = 'READ-COMMITTED';
3. 修改连接URL
在连接URL中正确设置事务隔离级别:
yaml
复制代码
spring:
datasource:
url: jdbc:mysql://localhost:3306/imooc-news-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&sessionVariables=tx_isolation='READ-COMMITTED'
4. 确认数据源属性配置
确保数据源属性配置正确:
yaml
复制代码
spring:
datasource:
hikari:
connection-timeout: 30000
minimum-idle: 5
maximum-pool-size: 20
auto-commit: true
idle-timeout: 600000
pool-name: DateSourceHikariCP
max-lifetime: 1800000
connection-test-query: SELECT 1
data-source-properties:
tx_isolation: 'READ-COMMITTED'
5. 确认使用MariaDB JDBC驱动
确保使用MariaDB的JDBC驱动:
Maven
xml
复制代码
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.2</version>
</dependency>
Gradle
groovy
复制代码
implementation 'org.mariadb.jdbc:mariadb-java-client:2.7.2'
6. 测试事务隔离设置
在MariaDB中手动测试设置事务隔离级别,确保语法正确:
sql
复制代码
SET SESSION tx_isolation = 'READ-COMMITTED';
如果这条命令可以成功执行,那么它也应该能在你的连接URL中正确设置。
7. 确认Spring配置
确保Spring Boot配置正确,可以通过显式设置数据源属性来避免问题:
yaml
复制代码
spring:
datasource:
hikari:
data-source-properties:
tx_isolation: 'READ-COMMITTED'
总结
通过上述步骤,你应该能够解决 tx_isolation 变量设置的问题,并确保连接到MariaDB数据库时没有语法错误。如果问题依然存在,请提供更多的错误日志和配置文件信息,以便进一步诊断。
http://writer.imoocnews.com:8003/doc.html 先接收验证码 通过redis获得 再测试登录接口
{
"mobile":"15027597319",
"smsCode":"815850"
}
相应内容:
{
"status": 200,
"msg": "操作成功!",
"success": true,
-"data": {
"id": "240629F0PD4PZANC",
"mobile": "15027597319",
"nickname": "用户:15******319",
"face": "https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg",
"realname": null,
"email": null,
"sex": 2,
"birthday": "2024-06-29 00:00:00",
"province": null,
"city": null,
"district": null,
"activeStatus": 0,
"totalIncome": 0,
"createdTime": "2024-06-29 19:39:10",
"updatedTime": "2024-06-29 19:39:10"
}
}
此时去数据库imooc-news-dev的app_user中发现并未有数据新增进入
再UserServiceimpl.java中
appUserMapper.insert(user);
当如果把app_user数据库离的active_status 的0变成2 就会被冻结【UserStatus】
{
"status": 507,
"msg": "用户已被冻结,请联系管理员!",
"success": false,
"data": null
}
设置会话与cookie信息【注册登录】
service-user com/imooc/user/controller/PassportController.java
...
// 3.保存用户分布式会话的相关操作
int userActiveStatus = user.getActiveStatus();
if (userActiveStatus != UserStatus.FROZEN.type){
String uToken = UUID.randomUUID().toString();
redis.set(REDIS_USER_TOKEN+":"+user.getId(),uToken);//BaseController里面 保存token到redis
//保存用户id和token到cookie中 设计一个request response 回到PassportControllerApi
}
return GraceJSONResult.ok(user);
}
...
service-api com/imooc/api/BaseController.java [增加一个setCookie]
package com.imooc.api;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
public static final String REDIS_USER_TOKEN = "redis_user_token";//ctrl+shift+u直接大写
public static final Integer COOKIE_MONTH = 30 * 24 * 60 * 60;
/**可以公用 就放到BaseController里面
* 在任何controller中都可以调用和使用
* 获取BO中的错误信息
*
* @param result
* @return
*/
public Map<String, String> getErrors(BindingResult result){
//对应着RegistLoginBO的信息
Map<String, String> map = new HashMap<>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList){
//发生验证错误所对应的某个属性
String field = error.getField();
//验证的错误信息
String msg = error.getDefaultMessage();
map.put(field, msg);
}
return map;
}
/* public void setCookie(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge){
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
Cookie cookie = new Cookie(cookieName,cookieValue);
cookie.setMaxAge(maxAge);
cookie.setDomain("imoocnews.com");
cookie.setPath("/");//都用cookie
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
} */
public void setCookie(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge){
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
// Cookie cookie = new Cookie(cookieName,cookieValue);
// cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
// cookie.setPath("/");//都用cookie
setCookieValue(request, response, cookieName, cookieValue, maxAge);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public void setCookieValue(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge){
Cookie cookie = new Cookie(cookieName,cookieValue);
cookie.setMaxAge(maxAge);
cookie.setDomain("imoocnews.com");
cookie.setPath("/");//都用cookie
response.addCookie(cookie);//把cookie传入
}
}
service-api com/imooc/api/controller/user/PassportControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; //用户需求验证
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
@RequestMapping("passport")
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request);
@ApiOperation(value = "一键注册登录接口",notes = "一键注册登录接口",httpMethod = "POST")
@PostMapping("/doLogin") //表单里面用post RequestBody后面传过来的东西和json对象对应
public GraceJSONResult doLogin(@RequestBody @Valid RegistLoginBO registLoginBO
, BindingResult result, HttpServletRequest request, HttpServletResponse response);
//完成之后 去BaseController里面写一个setCookie()方便都可以用
}
service-user com/imooc/user/controller/PassportController.java
@Override
public GraceJSONResult doLogin(RegistLoginBO registLoginBO, BindingResult result, HttpServletRequest request, HttpServletResponse response) {
//0.判断BindingResult中是否保存了错误的验证信息 如果有则需要返回
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
String mobile = registLoginBO.getMobile();
String smsCode = registLoginBO.getSmsCode();
//1.校验验证码是否匹配[在redis中去获取]
String redisSMSCode = redis.get(MOBILE_SMSCODE + ":" + mobile); //为空||不同值
if (StringUtils.isBlank(redisSMSCode) || !redisSMSCode.equalsIgnoreCase(smsCode)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
//2.查询数据库,判断该用户注册
AppUser user = userService.queryMobileIsExist(mobile);
if (user != null && user.getActiveStatus() == UserStatus.FROZEN.type){
//如果用户不为空,并且状态为冻结,则直接抛出异常,禁止登录
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_FROZEN);
}else if (user == null){
//如果用户没有注册过,则为null,需要注册信息入库
user = userService.createUser(mobile);
}
// 3.保存用户分布式会话的相关操作
int userActiveStatus = user.getActiveStatus();
if (userActiveStatus != UserStatus.FROZEN.type){
String uToken = UUID.randomUUID().toString();
redis.set(REDIS_USER_TOKEN+":"+user.getId(),uToken);//BaseController里面 保存token到redis
//保存用户id和token到cookie中 设计一个request response 回到PassportControllerApi
setCookie(request, response,"uToken",uToken,COOKIE_MONTH);
setCookie(request, response,"uid",user.getId(),COOKIE_MONTH);
}
// 4.用户登录或注册成功以后,需要删除redis中的短信验证码,验证码只能使用一次,用过则作废
redis.del(MOBILE_SMSCODE + ":" + mobile);
// 5.返回用户状态 返回前端看
return GraceJSONResult.ok(userActiveStatus);
}
资源属性与常量绑定 [优雅]
把这种属性放到常量文件里进行绑定 cookie.setDomain("imoocnews.com");
service-api com/imooc/api/BaseController.java
public abstract class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
public static final String REDIS_USER_TOKEN = "redis_user_token";//ctrl+shift+u直接大写
public static final Integer COOKIE_MONTH = 30 * 24 * 60 * 60;
★ @Value("${website.domain-name}") ★★
★ public String DOMAIN_NAME; ★★
...
public void setCookie(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge) {
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
// Cookie cookie = new Cookie(cookieName,cookieValue);
// cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
// cookie.setPath("/");//都用cookie
setCookieValue(request, response, cookieName, cookieValue, maxAge);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public void setCookieValue(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge) {
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
cookie.setDomain(DOMAIN_NAME);
cookie.setPath("/");//都用cookie
response.addCookie(cookie);//把cookie传入
}
...
================================================================================
application-dev.yml
server:
port: 8003
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
# setup CN from java, This is resource
website:
domain-name: imoocnews.com
查询用户账户信息
service-api com/imooc/api/controller/user/UserControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user")
public interface UserControllerApi {
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
}
service-user com/imooc/user/controller/UserController.java
package com.imooc.user.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.api.controller.user.UserControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.vo.UserAccountInfoVO;
import com.imooc.user.service.impl.UserService;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class UserController implements UserControllerApi {
final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@Override
public GraceJSONResult getAccountInfo(String userId) {
// 0. 判断参数不为空
if (StringUtils.isBlank(userId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.UN_LOGIN);
}
// 1. 根据userId查询用户的信息 UserService+impl
AppUser user = getUser(userId);
// 2. 返回用户信息
UserAccountInfoVO accountInfoVO = new UserAccountInfoVO();
BeanUtils.copyProperties(user, accountInfoVO); //拷贝信息
return GraceJSONResult.ok(accountInfoVO);
}
private AppUser getUser(String userId){
// TODO 本方法后续公用,并且扩展
AppUser user = userService.getUser(userId);
return user;
}
}
service-user com/imooc/user/service/impl/UserService.java
package com.imooc.user.service.impl;
import com.imooc.pojo.AppUser;
public interface UserService {
/**
* 判断用户是否存在,如果存在返回user信息
*/
public AppUser queryMobileIsExist(String mobile);
/**
* 创建用户,新增用户记录到数据库
*/
public AppUser createUser(String mobile);
/**
* 根据用户主键id查询用户信息
* @param userId
* @return
*/
public AppUser getUser(String userId);
}
====================================================================
service-user com/imooc/user/service/UserServiceimpl.java
@Override
public AppUser getUser(String userId) {
return appUserMapper.selectByPrimaryKey(userId);
}
dev-model com/imooc/pojo/vo/UserAccountInfoVO.java
public class UserAccountInfoVO {
private String id;
private String mobile;
private String nickname;
private String face;
private String realname;
private String email;
private Integer sex;
private Date birthday;
private String province;
private String city;
private String district;
}Getter + Setter
信息校验
service-user com/imooc/user/controller/UserController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.api.controller.user.UserControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.pojo.vo.UserAccountInfoVO;
import com.imooc.user.service.impl.UserService;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
public class UserController extends BaseController implements UserControllerApi {
final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@Override
public GraceJSONResult getAccountInfo(String userId) {
// 0. 判断参数不为空
if (StringUtils.isBlank(userId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.UN_LOGIN);
}
// 1. 根据userId查询用户的信息 UserService+impl
AppUser user = getUser(userId);
// 2. 返回用户信息
UserAccountInfoVO accountInfoVO = new UserAccountInfoVO();
BeanUtils.copyProperties(user, accountInfoVO); //拷贝信息
return GraceJSONResult.ok(accountInfoVO);
}
private AppUser getUser(String userId){
// TODO 本方法后续公用,并且扩展
AppUser user = userService.getUser(userId);
return user;
}
@Override
public GraceJSONResult updateUserInfo(UpdateUserInfoBO updateUserInfoBO, BindingResult result) {
// 0.校验BO
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
// 1.执行更新操作
return GraceJSONResult.ok();
}
}
service-api com/imooc/api/controller/user/UserControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user")
public interface UserControllerApi {
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
BindingResult result);
}
dev-model com/imooc/pojo/bo/UpdateUserInfoBO.java
public class UpdateUserInfoBO {
@NotBlank(message = "用户ID不能为空")
private String id;
@NotBlank(message = "用户昵称不能为空")
@Length(max = 12, message = "用户昵称不能超过12位")
private String nickname;
@NotBlank(message = "用户头像不能为空")
private String face;
@NotBlank(message = "真实姓名不能为空")
private String realname;
@Email
@NotBlank(message = "邮件不能为空")
private String email;
@NotNull(message = "请选择一个性别")
@Min(value = 0, message = "性别选择不正确")
@Max(value = 1, message = "性别选择不正确")
private Integer sex;
@NotNull(message = "请选择生日日期")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") // 解决前端日期字符串传到后端后,转换为Date类型
private Date birthday;
@NotBlank(message = "请选择所在城市")
private String province;
@NotBlank(message = "请选择所在城市")
private String city;
@NotBlank(message = "请选择所在城市")
private String district;
}
激活用户信息入库
service-user com/imooc/user/controller/UserController.java
@Override
public GraceJSONResult updateUserInfo(UpdateUserInfoBO updateUserInfoBO, BindingResult result) {
// 0.校验BO
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
// 1.执行更新操作
userService.updateUserInfo(updateUserInfoBO);
return GraceJSONResult.ok();
//调用UserService把独有信息传入
}
service-user com/imooc/user/service/impl/UserService.java
package com.imooc.user.service.impl;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.UpdateUserInfoBO;
public interface UserService {
/**
* 判断用户是否存在,如果存在返回user信息
*/
public AppUser queryMobileIsExist(String mobile);
/**
* 创建用户,新增用户记录到数据库
*/
public AppUser createUser(String mobile);
/**
* 根据用户主键id查询用户信息
* @param userId
* @return
*/
public AppUser getUser(String userId);
/**
* 用户修改信息,完善资料,并且激活
* @param updateUserInfoBO
*/
public void updateUserInfo(UpdateUserInfoBO updateUserInfoBO);
}
service-user com/imooc/user/service/UserServiceimpl.java
@Override
public void updateUserInfo(UpdateUserInfoBO updateUserInfoBO){
String userId = updateUserInfoBO.getId();
AppUser userInfo = new AppUser();
BeanUtils.copyProperties(updateUserInfoBO, userInfo);
userInfo.setUpdatedTime(new Date());
userInfo.setActiveStatus(UserStatus.ACTIVE.type);
//appUserMapper.updateByPrimaryKey()//数据中现有的数据覆盖为空的
int result = appUserMapper.updateByPrimaryKeySelective(userInfo);
if (result != 1){
//更新操作有问题
GraceException.display(ResponseStatusEnum.USER_UPDATE_ERROR);
}
}
查询并展示用户基本信息
service-api com/imooc/api/controller/user/UserControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user")
public interface UserControllerApi {
@ApiOperation(value = "获得用户基本信息",notes = "获得用户基本信息",httpMethod = "POST")
@PostMapping("/getUserInfo")
public GraceJSONResult getUserInfo(@RequestParam String userId);
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,BindingResult result);
}
dev-model com/imooc/pojo/vo/AppUserVO.java
public class AppUserVO {
private String id;
private String nickname;
private String face;
private Integer activeStatus;
}Getter+Setter
service-user com/imooc/user/controller/UserController.java
@RestController
public class UserController extends BaseController implements UserControllerApi {
final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@Override
public GraceJSONResult getUserInfo(String userId) {
//重写接口进行解耦!!
// 0. 判断参数不为空
if (StringUtils.isBlank(userId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.UN_LOGIN);
}
// 1. 根据userId查询用户的信息 UserService+impl
AppUser user = getUser(userId);
// 2. 返回用户信息
AppUserVO userVO = new AppUserVO();
BeanUtils.copyProperties(user, userVO); //拷贝信息
return GraceJSONResult.ok(userVO);
}
}
浏览器存储介质
缓存用户信息 [用Redis减轻数据库压力]
service-api com/imooc/api/BaseController.java
//REDIS_USER_INFO添加进来
public abstract class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
public static final String REDIS_USER_TOKEN = "redis_user_token";//ctrl+shift+u直接大写
public static final String REDIS_USER_INFO = "redis_user_info";//ctrl+shift+u直接大写
public static final Integer COOKIE_MONTH = 30 * 24 * 60 * 60;
@Value("${website.domain-name}")
public String DOMAIN_NAME;
/**
* 可以公用 就放到BaseController里面
* 在任何controller中都可以调用和使用
* 获取BO中的错误信息
*
* @param result
* @return
*/
public Map<String, String> getErrors(BindingResult result) {
//对应着RegistLoginBO的信息
Map<String, String> map = new HashMap<>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList) {
//发生验证错误所对应的某个属性
String field = error.getField();
//验证的错误信息
String msg = error.getDefaultMessage();
map.put(field, msg);
}
return map;
}
public void setCookie(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge) {
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
// Cookie cookie = new Cookie(cookieName,cookieValue);
// cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
// cookie.setPath("/");//都用cookie
setCookieValue(request, response, cookieName, cookieValue, maxAge);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public void setCookieValue(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge) {
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
cookie.setDomain(DOMAIN_NAME);
cookie.setPath("/");//都用cookie
response.addCookie(cookie);//把cookie传入
}
service-user com/imooc/user/controller/UserController.java
private AppUser getUser(String userId){
//查询判断redis中是否包含用户信息 若有则直接返回就不去查询数据库了
String userJson = redis.get(REDIS_USER_INFO + ":" + userId);
AppUser user = null;
if (StringUtils.isNotBlank(userJson)){
//字符串转换成json对象 要提取user 所以要一开始赋值null
user = JsonUtils.jsonToPojo(userJson, AppUser.class);
} else {
// TODO 本方法后续公用,并且扩展
user = userService.getUser(userId);
// 由于用户信息不怎么会变动,对于一些千万级别网站来说,这类信息不会直接去查询数据库
// 可以完全依靠Redis,直接把查询后的数据存入到Redis中
// set里面设置一个key去BaseController里设置 ↓user变成jason转换类
redis.set(REDIS_USER_INFO + ":" + userId, JsonUtils.objectToJson(user));
}
return user;
}
service-user com/imooc/user/service/UserServiceimpl.java
package com.imooc.user.service;
import com.imooc.enums.Sex;
import com.imooc.enums.UserStatus;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.user.mapper.AppUserMapper;
import com.imooc.user.service.impl.UserService;
import com.imooc.utils.DesensitizationUtil;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
import org.n3r.idworker.Sid;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import com.imooc.utils.DateUtil;
import java.util.Date;
@Service
public class UserServiceimpl implements UserService {
@Autowired
public AppUserMapper appUserMapper; //基本的CRUD都可以
@Autowired
public Sid sid;
public static final String REDIS_USER_INFO = "redis_user_info";//ctrl+shift+u直接大写
@Autowired
public RedisOperator redis;
private static final String USER_FACE0 = "https://raw.githubusercontent.com/P-luminary/images/10d94134b65e13cc8ec9b8a9aeae4f958921cab7/data/Imooc_Cat.jpg";
private static final String USER_FACE1 = "https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg";
@Override
public AppUser queryMobileIsExist(String mobile) {
Example userExample = new Example(AppUser.class);
Example.Criteria userCriteria = userExample.createCriteria();
userCriteria.andEqualTo("mobile", mobile);
AppUser user = appUserMapper.selectOneByExample(userExample);
return user;
}
@Transactional //对整个类的方法,事务起作用。无异常时正常提交,有异常时数据回滚
@Override
public AppUser createUser(String mobile) {
/**
* 互联网项目都要考虑可扩展性
* 如果未来的业务激增,那么就需要分表分库
* 那么数据库表主键id必须保证全局(全库)唯一,不得重复
*/
String userId = sid.nextShort();
AppUser user = new AppUser();
user.setId(userId);
user.setMobile(mobile);
user.setNickname("用户:" + DesensitizationUtil.commonDisplay(mobile)); //給手机号加** 是脱敏操作
user.setFace(USER_FACE1);
user.setBirthday(DateUtil.stringToDate("2024-06-29")); //字符串转换Date类型
user.setSex(Sex.secret.type);
user.setActiveStatus(UserStatus.INACTIVE.type);//是否激活
user.setTotalIncome(0);//收入
user.setCreatedTime(new Date());
user.setUpdatedTime(new Date());
appUserMapper.insert(user);
return user;
}
@Override
public AppUser getUser(String userId) {
return appUserMapper.selectByPrimaryKey(userId);
}
@Override
public void updateUserInfo(UpdateUserInfoBO updateUserInfoBO){
String userId = updateUserInfoBO.getId();
AppUser userInfo = new AppUser();
BeanUtils.copyProperties(updateUserInfoBO, userInfo);
userInfo.setUpdatedTime(new Date());
userInfo.setActiveStatus(UserStatus.ACTIVE.type);
//appUserMapper.updateByPrimaryKey()//数据中现有的数据覆盖为空的
int result = appUserMapper.updateByPrimaryKeySelective(userInfo);
if (result != 1){
//更新操作有问题
GraceException.display(ResponseStatusEnum.USER_UPDATE_ERROR);
}
// 再次查询用户的最新信息,放入redis中
AppUser user = getUser(userId);
redis.set(REDIS_USER_INFO + ":" + userId, JsonUtils.objectToJson(user));
}
}
Redis里面 redis_user_info
{"id":"240629F21AK1BHX4","mobile":"15027597319","nickname":"15027597319","face":"https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg","realname":"小宝宝的小潘潘2","email":"390415030@qq.com","sex":1,"birthday":1720195200000,"province":"河北","city":"唐山市","district":"丰润区","activeStatus":1,"totalIncome":0,"createdTime":1719661387000,"updatedTime":1720281759000}
双写数据不一致的情况 [redis故障没有写入新数据]
如何双写一致 缓存双删
用户先把老Redis中的数据删除 然后再把修改值放入数据库 然后数据库再导入redis 就可以保证双写一致
但是要保证数据库放入Redis之前 后期用户请求要再其之后 [进行休眠] =>缓存双删
service-user com/imooc/user/service/UserServiceimpl.java @Override
public void updateUserInfo(UpdateUserInfoBO updateUserInfoBO){
String userId = updateUserInfoBO.getId();
// 保证双写一致,先删除redis中的数据,后更新数据库
// redis.del(REDIS_USER_INFO + ":" + userId);
AppUser userInfo = new AppUser();
BeanUtils.copyProperties(updateUserInfoBO, userInfo);
userInfo.setUpdatedTime(new Date());
userInfo.setActiveStatus(UserStatus.ACTIVE.type);
//appUserMapper.updateByPrimaryKey()//数据中现有的数据覆盖为空的
int result = appUserMapper.updateByPrimaryKeySelective(userInfo);
if (result != 1){
//更新操作有问题
GraceException.display(ResponseStatusEnum.USER_UPDATE_ERROR);
}
// 再次查询用户的最新信息,放入redis中
AppUser user = getUser(userId);
redis.set(REDIS_USER_INFO + ":" + userId, JsonUtils.objectToJson(user));
// 缓存双删策略 [不处理可能会缓存击穿]
try {
Thread.sleep(100);
redis.del(REDIS_USER_INFO + ":" + userId);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
CAP理论 [只能满足其中一、二]
分布式系统都存在CAP情况
定理
CAP的重要性
分布式不可能同时满足三个条件 【先满足P再去考虑A或C】
CAP理论是什么?
- C(Consistency, 一致性):读操作是否总能读到前一个写操作的结果 [某节点获得的数据都是一样的] 在本项目中一致性位于Session Storage
- A(Availability, 可用性):非故障节点应该在合理的时间内作出合理的响应(不是错误或超时的响应),但是可能不是最新的数据。 [某个挂掉了 其他还可以用]
- P(Partition tolerance, 分区容错):当出现网络分区现象后,系统能够继续运行。分区容错性
CAP如何选择?
- CP[支付宝]或者AP[超级跑跑系统维护]
- 在什么场合,可用性高于一致性?
- 网页必须要保障可用性(一定能看到最重要 是不是最新的不重要)和分区容错
- 支付的时候一定要保障一致性(我可以保证不可用 但我不允许余额不一致)和分区容错
- 合适的才是最好的
- CP:Redis【保证数据一致性 一定要满足C】
- AP:会采用弱一致性 淘宝下单只需要知道下单就好 数量一致性商家可以慢慢调整
- CA:单体存在架构、关系型架构
在本项目中如果采用弱一致性:可以不把用户存到session Storage 直接显示
集群、分布式、微服务的区别
集群和分布式的区别
- 分布式:一个业务分拆多个子业务,部署在不同的服务器上 [服务器之间要通信]
- 集群:同一个业务,部署在多个服务器上 [五台机器可以不通信]
集群和微服务的区别
- 集群:分散压力
- 微服务:分散压力
微服务和分布式的区别
微服务是架构设计方式 [逻辑架构]
分布式是系统部署方式 [物理架构]
微服务:是一种架构方式 [大的服务拆成小的服务 每个服务独立开发测试]
分布式:主要强调部署的方式
用户会话拦截器 [必须用户登陆后才可以用其他界面]
service-api com/imooc/api/interceptors/UserTokenInterceptor.java
package com.imooc.api.interceptors;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserTokenInterceptor extends BaseInterceptor implements HandlerInterceptor {
/**
* 拦截请求,访问controller之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 有些接口同时会給安卓 H5等 所以不去cookie拿
String userId = request.getHeader("headerUserId");
String userToken = request.getHeader("headerUserToken");
// 判断是否放行
boolean run = verifyUserIdToken(userId, userToken, REDIS_USER_TOKEN);
/**
* false:请求被拦截
* true:请求通过验证,放行
*/
return true;
}
/**
* 请求访问到controller之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 请求访问到controller之后,渲染视图之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
package com.imooc.api.interceptors;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseInterceptor {
@Autowired
private RedisOperator redis;
public static final String REDIS_USER_TOKEN = "redis_user_token";//ctrl+shift+u直接大写
public boolean verifyUserIdToken(String id,
String token,
String redisKeyPrefix){ //redis..前缀
if (StringUtils.isNotBlank(id) && StringUtils.isNotBlank(token)){
String redisToken = redis.get(redisKeyPrefix + ":" + id);
if (StringUtils.isBlank(id)){
GraceException.display(ResponseStatusEnum.UN_LOGIN);
return false;
} else {
if (!redisToken.equalsIgnoreCase(token)){//是否和传入token一致
GraceException.display(ResponseStatusEnum.TICKET_INVALID);
return false;
}
}
}else {
GraceException.display(ResponseStatusEnum.UN_LOGIN);
return false;
}
return true;
}
}
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId");
}
}
/*
在你的 Spring 应用程序中,InterceptorConfig 类中的 @Bean 注解用于定义 PassportInterceptor 和 UserTokenInterceptor 的 bean。这使这些拦截器对象在 Spring 上下文中可用,以便进行依赖注入。
删除 @Bean 注解后会出现错误 Autowired members must be defined in valid Spring bean (@Component|@Service|...),这是因为 BaseInterceptor 类中有一个需要由 Spring 注入的依赖 (RedisOperator redis)。要让 Spring 执行依赖注入,包含 @Autowired 注解的类必须是一个由 Spring 管理的 bean,可以通过 @Component、@Service、@Controller 等注解或在配置类中通过 @Bean 来定义。
这里是对 @Bean 的作用以及为什么删除它会导致错误的详细解释:
使用 @Bean 定义 Bean:
在 InterceptorConfig 类中,@Bean 注解定义了 PassportInterceptor 和 UserTokenInterceptor 作为 Spring 的 bean。这使得它们在整个应用程序中可用于依赖注入。
依赖注入的要求:
BaseInterceptor 类中使用了 @Autowired 注解来注入 RedisOperator。要使这个注入有效,BaseInterceptor 必须是一个 Spring 管理的 bean。而 @Bean 注解在配置类中定义了这些拦截器,使得 Spring 可以管理它们,并在需要时进行依赖注入。
如果删除了 @Bean 注解,PassportInterceptor 和 UserTokenInterceptor 将不再是 Spring 管理的 bean,从而导致在它们内部或相关联的类(如 BaseInterceptor)中的依赖无法被注入。这就是为什么删除 @Bean 注解后会出现 Autowired members must be defined in valid Spring bean (@Component|@Service|...) 错误的原因。
*/
用户状态激活拦截器
service-api com/imooc/api/interceptors/UseActiveInterceptor.java
package com.imooc.api.interceptors;
import com.imooc.enums.UserStatus;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 用户激活状态检测拦截器
* 发文章,修改文章,删除文章,发表评论,查看评论等
* 这些接口都是要在用户激活后才能进行操作
* 否则需要提示用户前往[账号设置]去修改信息
*/
public class UseActiveInterceptor extends BaseInterceptor implements HandlerInterceptor {
/**
* 拦截请求,访问controller之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 有些接口同时会給安卓 H5等 所以不去cookie拿
String userId = request.getHeader("headerUserId");
String userJson = redis.get(REDIS_USER_INFO + ":" + userId);
AppUser user = null;
if (StringUtils.isNotBlank(userJson)){
user = JsonUtils.jsonToPojo(userJson, AppUser.class);
} else {
GraceException.display(ResponseStatusEnum.UN_LOGIN);
}
if (user.getActiveStatus() == null || user.getActiveStatus() != UserStatus.ACTIVE.type){
GraceException.display(ResponseStatusEnum.USER_INACTIVE_ERROR);
return false;
//随后去拦截器里进行@Bean注册 [下下个代码就是]
}
/**
* false:请求被拦截
* true:请求通过验证,放行
*/
return true;
}
/**
* 请求访问到controller之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 请求访问到controller之后,渲染视图之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
service-user com/imooc/user/controller/PassportController.java
//[加一行redis.set(REDIS_USER_INFO+":"+user.getId(), JsonUtils.objectToJson(user));]
@Override
public GraceJSONResult doLogin(RegistLoginBO registLoginBO, BindingResult result, HttpServletRequest request, HttpServletResponse response) {
//0.判断BindingResult中是否保存了错误的验证信息 如果有则需要返回
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
String mobile = registLoginBO.getMobile();
String smsCode = registLoginBO.getSmsCode();
//1.校验验证码是否匹配[在redis中去获取]
String redisSMSCode = redis.get(MOBILE_SMSCODE + ":" + mobile); //为空||不同值
if (StringUtils.isBlank(redisSMSCode) || !redisSMSCode.equalsIgnoreCase(smsCode)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
//2.查询数据库,判断该用户注册
AppUser user = userService.queryMobileIsExist(mobile);
if (user != null && user.getActiveStatus() == UserStatus.FROZEN.type){
//如果用户不为空,并且状态为冻结,则直接抛出异常,禁止登录
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_FROZEN);
}else if (user == null){
//如果用户没有注册过,则为null,需要注册信息入库
user = userService.createUser(mobile);
}
// 3.保存用户分布式会话的相关操作
int userActiveStatus = user.getActiveStatus();
if (userActiveStatus != UserStatus.FROZEN.type){
String uToken = UUID.randomUUID().toString();
redis.set(REDIS_USER_TOKEN+":"+user.getId(),uToken);//BaseController里面 保存token到redis
redis.set(REDIS_USER_INFO+":"+user.getId(), JsonUtils.objectToJson(user));
//保存用户id和token到cookie中 设计一个request response 回到PassportControllerApi
setCookie(request, response,"utoken",uToken,COOKIE_MONTH);
setCookie(request, response,"uid",user.getId(),COOKIE_MONTH);
}
// 4.用户登录或注册成功以后,需要删除redis中的短信验证码,验证码只能使用一次,用过则作废
// redis.del(MOBILE_SMSCODE + ":" + mobile);
// 5.返回用户状态 返回前端看
return GraceJSONResult.ok(userActiveStatus);
}
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UseActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UseActiveInterceptor useActiveInterceptor(){
return new UseActiveInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId");
// registry.addInterceptor(userTokenInterceptor())
// .addPathPatterns("/user/getAccountInfo")
}
}
AOP警告日志监控与sql打印 [切面AOP通知编程]
dev-common 引入aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
service-api com/imooc/api/aspect/ServiceLogAspect.java
package com.imooc.api.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ServiceLogAspect {
final static Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
/**
* AOP通知:
* 1.前置通知
* 2.后置通知
* 3.环绕通知 ★★
* 4.异常通知
* 5.最终通知
*/
//*是返回所有类型 匹配包的位置 *.* = 任意文件.任意后缀 (..)是任意类和任意方法
@Around("execution(* com.imooc.*.service.impl..*.*(..))")
public Object recordTimeOfService(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("==== 开始执行 {}.{} ====",
joinPoint.getTarget().getClass(),
joinPoint.getSignature().getName());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
long takeTime = end - start;
if (takeTime > 3000){
logger.error("当前执行耗时:{}",takeTime);
}else if (takeTime > 2000){
logger.warn("当前执行耗时:{}",takeTime);
}else {
logger.info("当前执行耗时:{}",takeTime);
}
return result;
}
}
====================================================================
http://writer.imoocnews.com:9090/imooc-news/writer/accountInfo.html
提交信息 看后台Terminal
service-user application-dev.yml #增加一个open mybatis log in dev
server:
port: 8003
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
# open mybatis log in dev
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# setup CN from java, This is resource
website:
domain-name: imoocnews.com
===================================================================
如果在未来发生sql错误可以通过sql输出来找到sql语句从而放入运行检查错误 (21 28行)
JDBC Connection [HikariProxyConnection@2054571226 wrapping org.mariadb.jdbc.MariaDbConnection@4b4b68f8] will not be managed by Spring
==> Preparing: UPDATE app_user SET nickname = ?,face = ?,realname = ?,email = ?,sex = ?,birthday = ?,province = ?,city = ?,district = ?,active_status = ?,updated_time = ? WHERE id = ?
==> Parameters: 15027597319(String), https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg(String), 小宝宝的小潘潘(String), 390415030@qq.com(String), 1(Integer), 2024-07-06 00:00:00.0(Timestamp), 河北(String), 唐山市(String), 丰润区(String), 1(Integer), 2024-07-07 22:41:09.862(Timestamp), 240629F21AK1BHX4(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@9176eb0]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@65bd9477] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1798162927 wrapping org.mariadb.jdbc.MariaDbConnection@4b4b68f8] will not be managed by Spring
==> Preparing: SELECT id,mobile,nickname,face,realname,email,sex,birthday,province,city,district,active_status,total_income,created_time,updated_time FROM app_user WHERE id = ?
==> Parameters: 240629F21AK1BHX4(String)
<== Columns: id, mobile, nickname, face, realname, email, sex, birthday, province, city, district, active_status, total_income, created_time, updated_time
<== Row: 240629F21AK1BHX4, 15027597319, 15027597319, https://raw.githubusercontent.com/P-luminary/images/875ad52658686e6cc3a8e0cd75d2a324a3d742a9/data/Imooc_Girl.jpg, 小宝宝的小潘潘, 390415030@qq.com, 1, 2024-07-06, 河北, 唐山市, 丰润区, 1, 0, 2024-06-29 19:43:07.0, 2024-07-07 22:41:09.0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@65bd9477]
41:10.009 [http-nio-8003-exec-3] INFO io.lettuce.core.EpollProvider - Starting without optional epoll library
41:10.010 [http-nio-8003-exec-3] INFO io.lettuce.core.KqueueProvider - Starting without optional kqueue library
41:10.460 [http-nio-8003-exec-3] INFO c.imooc.api.aspect.ServiceLogAspect - 当前执行耗时:601
退出登录、注销会话
service-api com/imooc/api/controller/user/PassportControllerApi.java
//用户登录信息的redis和cookies清除
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid; //用户需求验证
@Api(value = "用户注册登录",tags = {"用户注册登录的Controller"})
@RequestMapping("passport")
public interface PassportControllerApi {
@ApiOperation(value = "获得短信验证码",notes = "获得短信验证码",httpMethod = "GET")
@GetMapping("/getSMSCode")
public GraceJSONResult getSMSCode(@RequestParam String mobile, HttpServletRequest request);
@ApiOperation(value = "一键注册登录接口",notes = "一键注册登录接口",httpMethod = "POST")
@PostMapping("/doLogin") //表单里面用post RequestBody后面传过来的东西和json对象对应
public GraceJSONResult doLogin(@RequestBody @Valid RegistLoginBO registLoginBO
, BindingResult result, HttpServletRequest request, HttpServletResponse response);
//完成之后 去BaseController里面写一个setCookie()方便都可以用
@ApiOperation(value = "用户退出登录",notes = "用户退出登录",httpMethod = "POST")
@PostMapping("/logout")
public GraceJSONResult logout(@RequestParam String userId,
HttpServletRequest request,
HttpServletResponse response);
}
service-user com/imooc/user/controller/PassportController.java
@Override
public GraceJSONResult logout(String userId,
HttpServletRequest request,
HttpServletResponse response){
redis.del(REDIS_USER_TOKEN + ":" + userId);
//USER_INFO可以不用删 可能后面会查询 没有清除cookie只有重新设置时间为0
setCookie(request, response, "utoken","",COOKIE_DELETE);
setCookie(request, response, "uid","",COOKIE_DELETE);
return GraceJSONResult.ok();
}
FastDFS架构原理与上传下载流程解析
文件服务器
配置FastDFS环境准备工作
环境准备
名称 | 说明 |
---|---|
Centos | 7.x |
libfastcommon-1.0.42.tar.gz | FastDFS分离出的一些公用函数包 |
FastDFS | FastDFS本体 |
fastdfs-nginx-module-1.22.tar.gz | FastDFS和nginx的关联模块 |
nginx | nginx1.15.4 |
它跟我说要准备两个虚拟机[tracker 和 storage 版本都是CentOS 7.x]
//创建一个FastDFS文件夹
[imooc@imooc FastDFS]$ ll
总用量 1980
-rw-rw-r--. 1 imooc imooc 800157 7月 9 15:53 fastdfs-6.04.tar.gz
-rw-rw-r--. 1 imooc imooc 19952 7月 9 15:53 fastdfs-nginx-module-1.22.tar.gz
-rw-rw-r--. 1 imooc imooc 164704 7月 9 15:53 libfastcommon-1.0.42.tar.gz
-rw-rw-r--. 1 imooc imooc 1032630 7月 9 15:53 nginx-1.16.1.tar.gz
[imooc@imooc FastDFS]$ tar -zxvf libfastcommon-1.0.42.tar.gz
[imooc@imooc FastDFS]$ cd libfastcommon-1.0.42/
[imooc@imooc libfastcommon-1.0.42]$ ll
总用量 32
drwxrwxr-x. 2 imooc imooc 114 12月 5 2019 doc
-rw-rw-r--. 1 imooc imooc 10054 12月 5 2019 HISTORY
-rw-rw-r--. 1 imooc imooc 674 12月 5 2019 INSTALL
-rw-rw-r--. 1 imooc imooc 1607 12月 5 2019 libfastcommon.spec
-rwxrwxr-x. 1 imooc imooc 3253 12月 5 2019 make.sh
drwxrwxr-x. 2 imooc imooc 191 12月 5 2019 php-fastcommon
-rw-rw-r--. 1 imooc imooc 2776 12月 5 2019 README
drwxrwxr-x. 3 imooc imooc 4096 12月 5 2019 src
[imooc@imooc libfastcommon-1.0.42]$ ./make.sh
[imooc@imooc libfastcommon-1.0.42]$ sudo ./make.sh install
//安装解压包的本体
[imooc@imooc FastDFS]$ tar -zxvf fastdfs-6.04.tar.gz
[imooc@imooc FastDFS]$ cd fastdfs-6.04/
[imooc@imooc fastdfs-6.04]$ ./make.sh
[imooc@imooc fastdfs-6.04]$ sudo ./make.sh install
[imooc@imooc fastdfs-6.04]$ cd /usr/bin
[imooc@imooc bin]$ ls fdfs_*
fdfs_appender_test fdfs_download_file fdfs_test
fdfs_appender_test1 fdfs_file_info fdfs_test1
fdfs_append_file fdfs_monitor fdfs_trackerd
fdfs_crc32 fdfs_regenerate_filename fdfs_upload_appender
fdfs_delete_file fdfs_storaged fdfs_upload_file
[imooc@imooc bin]$ cd /etc/fdfs/
[imooc@imooc fdfs]$ ll
总用量 28 //这些都是配置文件 如果要修改则需要拷贝一份新鲜的
-rw-r--r--. 1 root root 1834 7月 9 16:02 client.conf.sample
-rw-r--r--. 1 root root 10085 7月 9 16:02 storage.conf.sample
-rw-r--r--. 1 root root 527 7月 9 16:02 storage_ids.conf.sample
-rw-r--r--. 1 root root 8038 7月 9 16:02 tracker.conf.sample
[imooc@imooc FastDFS]$ cd fastdfs-6.04/
[imooc@imooc fastdfs-6.04]$ cd conf/
[imooc@imooc conf]$ ll
总用量 88
-rw-rw-r--. 1 imooc imooc 23981 12月 5 2019 anti-steal.jpg
-rw-rw-r--. 1 imooc imooc 1834 12月 5 2019 client.conf
-rw-rw-r--. 1 imooc imooc 955 12月 5 2019 http.conf
-rw-rw-r--. 1 imooc imooc 31172 12月 5 2019 mime.types
-rw-rw-r--. 1 imooc imooc 10085 12月 5 2019 storage.conf
-rw-rw-r--. 1 imooc imooc 527 12月 5 2019 storage_ids.conf
-rw-rw-r--. 1 imooc imooc 8038 12月 5 2019 tracker.conf
//拷贝到etc下 安装前的准备工作
[imooc@imooc conf]$ sudo cp * /etc/fdfs/
[imooc@imooc conf]$ cd /etc/fdfs
[imooc@imooc fdfs]$ ll
总用量 116
-rw-r--r--. 1 root root 23981 7月 9 16:06 anti-steal.jpg
-rw-r--r--. 1 root root 1834 7月 9 16:06 client.conf
-rw-r--r--. 1 root root 1834 7月 9 16:02 client.conf.sample
-rw-r--r--. 1 root root 955 7月 9 16:06 http.conf
-rw-r--r--. 1 root root 31172 7月 9 16:06 mime.types
-rw-r--r--. 1 root root 10085 7月 9 16:06 storage.conf
-rw-r--r--. 1 root root 10085 7月 9 16:02 storage.conf.sample
-rw-r--r--. 1 root root 527 7月 9 16:06 storage_ids.conf
-rw-r--r--. 1 root root 527 7月 9 16:02 storage_ids.conf.sample
-rw-r--r--. 1 root root 8038 7月 9 16:06 tracker.conf
-rw-r--r--. 1 root root 8038 7月 9 16:02 tracker.conf.sample
配置tracker服务 [一个虚拟机]
//根据配置文件去区分是哪个服务
[imooc@imooc fdfs]$ cd /etc/fdfs
[imooc@imooc fdfs]$ ll
总用量 116
-rw-r--r--. 1 root root 23981 7月 9 16:06 anti-steal.jpg
-rw-r--r--. 1 root root 1834 7月 9 16:06 client.conf
-rw-r--r--. 1 root root 1834 7月 9 16:02 client.conf.sample
-rw-r--r--. 1 root root 955 7月 9 16:06 http.conf
-rw-r--r--. 1 root root 31172 7月 9 16:06 mime.types
-rw-r--r--. 1 root root 10085 7月 9 16:06 storage.conf
-rw-r--r--. 1 root root 10085 7月 9 16:02 storage.conf.sample
-rw-r--r--. 1 root root 527 7月 9 16:06 storage_ids.conf
-rw-r--r--. 1 root root 527 7月 9 16:02 storage_ids.conf.sample
-rw-r--r--. 1 root root 8038 7月 9 16:06 tracker.conf
-rw-r--r--. 1 root root 8038 7月 9 16:02 tracker.conf.sample
[imooc@imooc fdfs]$ sudo vim tracker.conf
//里面的port=22122 bind_addr= 计算机节点 这些不动
//修改里面的base_path=/home/yuqing/fastdfs
//修改为→ /usr/local/fastdfs/tracker
[imooc@imooc fdfs]$ mkdir /usr/local/fastdfs/tracker -p //-p后面文件夹做递归创建
[imooc@imooc fdfs]$ sudo /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf //当成配置文件加进去 ★★★★★★★★★★★★★★★★★★
[imooc@imooc fdfs]$ ps -ef|grep tracker
root 6254 1 0 18:31 ? 00:00:00 /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf
imooc 6268 3011 0 18:31 pts/0 00:00:00 grep --color=auto tracker
配置storage服务 [另一个虚拟机]
[storage@imooc fdfs]$ cd /etc/fdfs/
[storage@imooc fdfs]$ sudo vim storage.conf
//[修改后] group_name=imooc
//[修改后] bath_path=/usr/local/fastdfs/storage
[storage@imooc fdfs]$ sudo mkdir /usr/local/fastdfs/storage -p
[storage@imooc fdfs]$ cd /usr/local/
[storage@imooc local]$ ll
[storage@imooc local]$ cd fastdfs/
[storage@imooc fastdfs]$ ll
[storage@localhost fastdfs]$ ll
总用量 0
drwxr-xr-x. 2 root root 6 7月 9 18:38 storage
[storage@localhost fastdfs]$ cd /etc/fdfs/ //接着修改storage
[storage@imooc fdfs]$ sudo vim storage.conf
//[修改后] store_path0=/usr/local/fastdfs/storage
//配置到tracker的ip地址[修改后] tracker_server=192.168.170.135:22122
/ ‘/8888’ http.server_port=8888 是web的相关端口号
[storage@localhost fdfs]$ sudo /usr/bin/fdfs_storaged /etc/fdfs/storage.conf //★★★★
★一定要先启动tracker 再去启动storage 不然service发不过去★
//配置客户端做上传动作
[imooc@imooc ~]$ cd /etc/fdfs
[storage@localhost fdfs]$ pwd
/etc/fdfs
[storage@localhost fdfs]$ sudo vim client.conf
//[修改后]base_path=/usr/local/fastdfs/client
[storage@localhost fdfs]$ sudo mkdir /usr/local/fastdfs/client
[storage@localhost fdfs]$ cd /usr/local/fastdfs
[storage@localhost fastdfs]$ ll
总用量 0
drwxr-xr-x. 2 root root 6 7月 9 19:26 client
drwxr-xr-x. 4 root root 30 7月 9 19:04 storage
[storage@localhost fastdfs]$ cd /etc/fdfs/
[storage@localhost fdfs]$ sudo vim client.conf
//[修改后]tracker_server=192.168.170.135:22122
[storage@localhost fdfs]$ cd /usr/bin
[storage@localhost bin]$ ls fdfs*
fdfs_appender_test fdfs_download_file fdfs_test
fdfs_appender_test1 fdfs_file_info fdfs_test1
fdfs_append_file fdfs_monitor fdfs_trackerd
fdfs_crc32 fdfs_regenerate_filename fdfs_upload_appender
fdfs_delete_file fdfs_storaged fdfs_upload_file
//fdfs_test在命令行去测试
[storage@localhost bin]$ cd /home/
[storage@localhost home]$ cd /usr/local/fastdfs/storage/
[storage@localhost storage]$ cd data
[storage@localhost data]$ cd 00
[storage@localhost data]$ ll 【里面很多十六进制数据】
[storage@localhost data]$ cd 00
[storage@localhost data]$ ll //【里面没有数据 上传图片到这里查看是否成功】
///home/storage 这里有一张测试图片log.png [自行添加]
[storage@localhost 00]$ pwd
/usr/local/fastdfs/storage/data/00/00
[storage@localhost 00]$ cd /etc/fdfs
[storage@localhost ~]$ cd /etc/fdfs/
[storage@localhost fdfs]$ cd /usr/bin/
[storage@localhost bin]$ ls fdfs*
fdfs_appender_test fdfs_download_file //fdfs_test
fdfs_appender_test1 fdfs_file_info fdfs_test1
fdfs_append_file fdfs_monitor fdfs_trackerd
fdfs_crc32 fdfs_regenerate_filename fdfs_upload_appender
fdfs_delete_file fdfs_storaged fdfs_upload_file
[storage@localhost bin]$ ./fdfs_test /etc/fdfs/client.conf upload /home/storage/log.png
/*
This is FastDFS client test program v6.04
Copyright (C) 2008, Happy Fish / YuQing
FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.fastken.com/
for more detail.
[2024-07-09 19:39:25] DEBUG - base_path=/usr/local/fastdfs/client, connect_timeout=10, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0
tracker_query_storage_store_list_without_group:
server 1. group_name=, ip_addr=192.168.170.136, port=23000
group_name=imooc【企业简写】, ip_addr=192.168.170.136, port=23000
storage_upload_by_filename
group_name=imooc, remote_filename=M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
【remote_filename:重组路径】【因为还没有发布文件服务 所以无法直接查看文件】
source ip address: 192.168.170.136
file timestamp=2024-07-09 19:39:25
file size=12618
file crc32=630904148
example file url: http://192.168.170.136/imooc/M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
storage_upload_slave_by_filename
group_name=imooc, remote_filename=M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png
source ip address: 192.168.170.136
file timestamp=2024-07-09 19:39:25
file size=12618
file crc32=630904148
example file url: http://192.168.170.136/imooc/M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png
*/
[storage@localhost bin]$ cd /usr/local/fastdfs/storage/data/
[storage@localhost data]$ cd 00
[storage@localhost 00]$ cd 00
[storage@localhost 00]$ ll
总用量 40
-rw-r--r--. 1 root root 12618 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png
-rw-r--r--. 1 root root 49 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png-m
-rw-r--r--. 1 root root 12618 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
-rw-r--r--. 1 root root 49 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png-m
安装Nginx提供Web服务 [通过浏览器访问到文件]
Nginx是反向代理服务器可以做集群 也可以控制多个虚拟主机
-rw-rw-r--. 1 storage storage 142245547 7月 10 15:32 jdk-7u75-linux-x64.tar.gz
-rw-rw-r--. 1 storage storage 1032630 7月 10 15:33 nginx-1.16.1.tar.gz
//[storage@localhost ~]$ sudo yum install gcc-c++
已加载插件:fastestmirror, langpacks
Determining fastest mirrors
//[storage@localhost ~]$ sudo yum install -y pcre pcre-devel
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
//[storage@localhost ~]$ sudo yum install -y zlib zlib-devel
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
//[storage@localhost ~]$ sudo yum install -y openssl openssl-devel
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
//[storage@localhost ~]$ tar -zxvf nginx-1.16.1.tar.gz
nginx-1.16.1.tar.gz
//[storage@localhost ~]$ cd nginx-1.16.1.tar.gz
[storage@localhost nginx-1.16.1]$ sudo mkdir /var/temp/nginx -p
//创建所需的临时目录:
sudo mkdir -p /var/temp/nginx/client
sudo mkdir -p /var/temp/nginx/proxy
sudo mkdir -p /var/temp/nginx/fastcgi
sudo mkdir -p /var/temp/nginx/uwsgi
sudo mkdir -p /var/temp/nginx/scgi
[storage@localhost nginx-1.16.1]$ ./configure \ //【预配置】
> --prefix=/usr/local/nginx \
> --pid-path=/var/run/nginx/nginx.pid \
> --lock-path=/var/lock/nginx.lock \
> --error-log-path=/var/log/nginx/error.log \
> --http-log-path=/var/log/nginx/access.log \
> --with-http_gzip_static_module \
> --http-client-body-temp-path=/var/temp/nginx/client \
> --http-proxy-temp-path=/var/temp/nginx/proxy \
> --http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
> --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
> --http-scgi-temp-path=/var/temp/nginx/scgi
[storage@localhost nginx-1.16.1]$ make //【编译】
/* linux中的网络不可达
如果镜像出了问题 一定要换一下镜像配置
1. 编辑 CentOS 的 YUM 配置文件:
编辑 /etc/yum.repos.d/CentOS-Base.repo 文件:
复制代码
sudo vi /etc/yum.repos.d/CentOS-Base.repo
2. 使用以下内容更新 CentOS-Base.repo 文件:
复制代码
[base]
name=CentOS-$releasever - Base
baseurl=http://vault.centos.org/7.9.2009/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[updates]
name=CentOS-$releasever - Updates
baseurl=http://vault.centos.org/7.9.2009/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[extras]
name=CentOS-$releasever - Extras
baseurl=http://vault.centos.org/7.9.2009/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
*/
[storage@localhost nginx-1.16.1]$ sudo make install
[storage@localhost nginx-1.16.1]$ cd /usr/local
[storage@localhost local]$ ll
总用量 0
drwxr-xr-x. 2 root root 6 4月 11 2018 bin
drwxr-xr-x. 2 root root 6 4月 11 2018 etc
drwxr-xr-x. 4 root root 35 7月 9 19:26 fastdfs
drwxr-xr-x. 2 root root 6 4月 11 2018 games
drwxr-xr-x. 2 root root 6 4月 11 2018 include
drwxr-xr-x. 2 root root 6 4月 11 2018 lib
drwxr-xr-x. 2 root root 6 4月 11 2018 lib64
drwxr-xr-x. 2 root root 6 4月 11 2018 libexec
drwxr-xr-x. 5 root root 42 7月 10 16:55 nginx
drwxr-xr-x. 2 root root 6 4月 11 2018 sbin
drwxr-xr-x. 5 root root 49 7月 9 17:16 share
drwxr-xr-x. 2 root root 6 4月 11 2018 src
[storage@localhost local]$ cd nginx/
[storage@localhost nginx]$ ll
总用量 4
drwxr-xr-x. 2 root root 4096 7月 10 16:55 conf
drwxr-xr-x. 2 root root 40 7月 10 16:55 html
drwxr-xr-x. 2 root root 19 7月 10 16:55 sbin
[storage@localhost nginx]$ cd sbin/
[storage@localhost sbin]$ ll
总用量 3768
-rwxr-xr-x. 1 root root 3857144 7月 10 16:55 nginx
[storage@localhost sbin]$ sudo ./nginx
[storage@localhost sbin]$ ps -ef|grep nginx
root 6642 1 0 16:58 ? 00:00:00 nginx: master process ./nginx
nobody 6643 6642 0 16:58 ? 00:00:00 nginx: worker process
storage 6651 2975 0 16:58 pts/0 00:00:00 grep --color=auto nginx
// 在浏览器输入:http://192.168.170.136/ 【如果没显示应该是虚拟机的防火墙拦截 可以禁止防火墙】
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working. Further configuration is required.
For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.
Thank you for using nginx.
[storage@localhost nginx]$ cd html
[storage@localhost html]$ ll
总用量 8
-rw-r--r--. 1 root root 494 7月 10 16:55 50x.html
-rw-r--r--. 1 root root 612 7月 10 16:55 index.html
[storage@localhost html]$ sudo ../sbin/nginx -t //【测试刚刚的步骤是否正确】
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
整合Nginx实现文件服务器
[storage@localhost FastDFS]$ tar -zxvf fastdfs-nginx-module-1.22.tar.gz
fastdfs-nginx-module-1.22/
fastdfs-nginx-module-1.22/HISTORY
fastdfs-nginx-module-1.22/INSTALL
fastdfs-nginx-module-1.22/src/
fastdfs-nginx-module-1.22/src/common.c
fastdfs-nginx-module-1.22/src/common.h
fastdfs-nginx-module-1.22/src/config
fastdfs-nginx-module-1.22/src/mod_fastdfs.conf
fastdfs-nginx-module-1.22/src/ngx_http_fastdfs_module.c
[storage@localhost FastDFS]$ cd fastdfs-nginx-module-1.22/
[storage@localhost fastdfs-nginx-module-1.22]$ ll
总用量 8
-rw-rw-r--. 1 storage storage 3036 11月 19 2019 HISTORY
-rw-rw-r--. 1 storage storage 2001 11月 19 2019 INSTALL
drwxrwxr-x. 2 storage storage 109 11月 19 2019 src
[storage@localhost fastdfs-nginx-module-1.22]$ cd src/
[storage@localhost src]$ sudo cp mod_fastdfs.conf /etc/fdfs/
[storage@localhost ~]$ cd /etc/fdfs/
[storage@localhost fdfs]$ sudo vim mod_fastdfs.conf
//【布置存储路径】
/*
store_path0=/usr/local/fastdfs/storage
tracker_server=192.168.170.135:22122
group_name=imooc
url_have_group_name = true
base_path=/usr/local/fastdfs/tmp
*/
[storage@localhost FastDFS]$ cd fastdfs-nginx-module-1.22/
[storage@localhost fastdfs-nginx-module-1.22]$ cd src/
[storage@localhost src]$ vim config
/local 把带有local的都删掉
[storage@localhost ~]$ cd nginx-1.16.1/
[storage@localhost nginx-1.16.1]$
./configure \
> --prefix=/usr/local/nginx \
> --pid-path=/var/run/nginx/nginx.pid \
> --lock-path=/var/lock/nginx.lock \
> --error-log-path=/var/log/nginx/error.log \
> --http-log-path=/var/log/nginx/access.log \
> --with-http_gzip_static_module \
> --http-client-body-temp-path=/var/temp/nginx/client \
> --http-proxy-temp-path=/var/temp/nginx/proxy \
> --http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
> --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
> --http-scgi-temp-path=/var/temp/nginx/scgi \
> --add-module=/home/storage/FastDFS/fastdfs-nginx-module-1.22/src
[storage@localhost nginx-1.16.1]$ sudo make && sudo make install
[storage@localhost nginx-1.16.1]$ cd /usr/local/nginx/
[storage@localhost nginx]$ cd conf/
[storage@localhost conf]$ sudo vim nginx.conf
/*
server {
listen 8888;
server_name localhost;
location ~/group[0-9]/ {
ngx_fastdfs_module;
}
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
*/
[storage@localhost conf]$ vim /etc/fdfs/tracker.conf
/http.service_port:8080
[storage@localhost conf]$ sudo vim nginx.conf //★★★★★★
/*
server {
listen 8888;
server_name localhost;
location /imooc/M00 {
ngx_fastdfs_module;
}
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
}
*/
[storage@localhost conf]$ sudo ../sbin/nginx -t //测试一下有无问题
ngx_http_fastdfs_set pid=6143
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[storage@localhost conf]$ sudo ../sbin/nginx -s reload //重新加载
ngx_http_fastdfs_set pid=6436
[storage@localhost conf]$ cd /usr/local/fastdfs/storage/
[storage@localhost storage]$ cd data
[storage@localhost data]$ cd 00/00
总用量 40
-rw-r--r--. 1 root root 12618 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png
-rw-r--r--. 1 root root 49 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457_big.png-m
-rw-r--r--. 1 root root 12618 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
-rw-r--r--. 1 root root 49 7月 9 19:39 wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png-m
http://192.168.170.136:8888/imooc/M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
//查看错误日志 ★★★【sudo tail -n 50 /var/log/nginx/error.log】★★★
/*
2. 重新加载 systemd 并启动 Nginx
重新加载 systemd:
复制代码
sudo systemctl daemon-reload
启动 Nginx:
sh
复制代码
sudo systemctl start nginx
设置开机自启动:
sh
复制代码
sudo systemctl enable nginx
检查 Nginx 服务状态:
sh
复制代码
sudo systemctl status nginx
*/
[storage@localhost conf]$ sudo vim /etc/fdfs/tracker.conf
[storage@localhost conf]$ sudo vim /etc/fdfs/storage.conf
[storage@localhost conf]$ cd /usr/local/nginx/conf/
[storage@localhost conf]$ sudo ../sbin/nginx -s stop
ngx_http_fastdfs_set pid=12586
[storage@localhost conf]$ sudo ../sbin/nginx
ngx_http_fastdfs_set pid=12605
[storage@localhost conf]$ sudo ../sbin/nginx -s reload
/*
FastDFS输出报告位置:
sudo tail -n 50 /usr/local/fastdfs/storage/logs/storaged.log
启动 Tracker 服务器:
sudo systemctl start fdfs_trackerd
检查 Tracker 服务器状态:
sudo systemctl status fdfs_trackerd
确认 Tracker 服务器监听端口:
sudo netstat -tuln | grep :22122
*/
//草!好几个小时的含泪史 一定要先开tracker端!!!
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
★★★★★一定要先启动tracker 再去启动storage 不然service发不过去★★★★★
/*
首先,重新启动 FastDFS 的 tracker 和 storage 服务:
bash
复制代码
# 重启 tracker 服务
sudo systemctl restart fdfs_trackerd
# 重启 storage 服务
sudo systemctl restart fdfs_storaged
2. 重启 Nginx 服务
接下来,重新启动 Nginx 服务,确保它能够加载新的配置并生效:
bash
复制代码
sudo systemctl restart nginx
3. 验证服务状态
重新启动服务后,可以通过以下方式验证它们的运行状态:
检查 FastDFS 服务状态:
bash
复制代码
sudo systemctl status fdfs_trackerd
sudo systemctl status fdfs_storaged
检查 Nginx 服务状态:
bash
复制代码
sudo systemctl status nginx
*/
http://192.168.170.136:8888/imooc/M00/00/00/wKiqiGaNIW2AMDeaAAAxSiWa1VQ457.png
创建文件服务module [文件上传]
【新建一个module imooc-news-dev-service-files】
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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>imooc-news-dev-service-files</artifactId>
<!--
imooc-news-dev-service-files
文件服务,文件相关的操作都在此文件中进行
文件上传 文件下载
fastdfs oss gridfs
-->
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 文件上传fdfs工具包 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.27.2</version>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
application.yuml
############################################################
#
# 用户微服务
# web访问端口号 约定:8003
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-file
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
servlet:
multipart:
max-file-size: 512000 #请求文件大小限制为500kb
max-request-size: 512000
application-dev.yuml
server:
port: 8004
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
service-files com/imooc/files/controller/HelloController.java
package com.imooc.files.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello(){
return GraceJSONResult.ok("Hello World!");
}
}
service-files com/imooc/files/Application.java
package com.imooc.files;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //排除数据源
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
-----------------------------------------------------------------------
http://localhost:8004/hello
{
"status": 200,
"msg": "操作成功!",
"success": true,
"data": "Hello World!"
}
整合fdfs与service实现 [文件上传]
service-api com/imooc/files/service/impl/UploaderServiceImpl.java
package com.imooc.files.service.impl;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.imooc.files.service.UploaderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Service
public class UploaderServiceImpl implements UploaderService {
//注入客户端
@Autowired
public FastFileStorageClient fastFileStorageClient;
@Override
public String uploadFdfs(MultipartFile file, String fileExtName) throws IOException {
StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), fileExtName, null);
return storePath.getFullPath();
}
}
service-api com/imooc/files/service/UploaderService.java
package com.imooc.files.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface UploaderService {
public String uploadFdfs(MultipartFile file, String fileExtName) throws IOException;
}
application.yml
############################################################
#
# 用户微服务
# web访问端口号 约定:8003
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-file
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
servlet:
multipart:
max-file-size: 512000 #请求文件大小限制为500kb
max-request-size: 512000
############################################################
#
# fdfs配置信息
#
############################################################
fdfs:
connect-timeout: 30
so-timeout: 30
tracker-list: 192.168.170.135:22122
实现fastdfs图片存储 [文件上传]
service-api com/imooc/api/controller/files/FileUploadControllerApi.java
package com.imooc.api.controller.files;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploadControllerApi {
@ApiOperation(value = "上传用户头像",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
}
service-files com/imooc/files/controller/FileUploadController.java
package com.imooc.files.controller;
import com.imooc.api.controller.files.FileUploadControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.files.service.UploaderService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController implements FileUploadControllerApi {
final static Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@Autowired
private UploaderService uploaderService;
@Override
public GraceJSONResult uploadFace(String userId,
MultipartFile file) throws Exception {
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_FORMATTER_FAILD);
}
// 执行上传
path = uploaderService.uploadFdfs(file, suffix);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
logger.info("path = " + path);
return GraceJSONResult.ok(path);
}
}
//此时去上传图片会报错 报跨域异常错误
//需要在用户service-user里的Application
//@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
//贴到com/imooc/files/Application.java
此时再次 http://writer.imoocnews.com:9090/imooc-news/writer/accountInfo.html
提交头像
Console:
06:09.827 [http-nio-8004-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
06:09.827 [http-nio-8004-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
06:09.832 [http-nio-8004-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 5 ms
06:09.882 [http-nio-8004-exec-2] INFO c.imooc.api.aspect.ServiceLogAspect - ==== 开始执行 class com.imooc.files.service.impl.UploaderServiceImpl.uploadFdfs ====
06:09.937 [http-nio-8004-exec-2] INFO c.imooc.api.aspect.ServiceLogAspect - 当前执行耗时:55
06:09.937 [http-nio-8004-exec-2] INFO c.i.f.c.FileUploadController - path = imooc/M00/00/00/wKiqiGaPrpKAEt22AAAeb3kUsrg507.png
http://192.168.170.136:8888/imooc/M00/00/00/wKiqiGaPrpKAEt22AAAeb3kUsrg507.png
此时就可以看到Cat的图片了!
完善用户头像上传
【在用户返回的时候写死路径+path】
return GraceJSONResult.ok("http://192.168.170.136:8888/"path);
给它包装一下 FileResource写一下
service-files com/imooc/files/controller/FileUploadController.java
package com.imooc.files.controller;
import com.imooc.api.controller.files.FileUploadControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.files.FileResource;
import com.imooc.files.service.UploaderService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController implements FileUploadControllerApi {
final static Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@Autowired
private UploaderService uploaderService;
@Autowired
private FileResource fileResource;
@Override
public GraceJSONResult uploadFace(String userId,
MultipartFile file) throws Exception {
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_FORMATTER_FAILD);
}
// 执行上传
path = uploaderService.uploadFdfs(file, suffix);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
logger.info("path = " + path);
String finalPath = "";
if (StringUtils.isNotBlank(path)){
finalPath = fileResource.getHost() + path;
} else{
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
return GraceJSONResult.ok(finalPath);
}
}
service-files com/imooc/files/FileResource.java
package com.imooc.files;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:file-${spring.profiles.active}.properties ") //这个是在application.yml里面的 自动匹配
@ConfigurationProperties(prefix = "file")
public class FileResource {
private String host;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}
file-dev.properties
# fastdfs storage 节点地址(nginx整合的web服务)
file.host=http://192.168.170.136:8888/
application.yml
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-file
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
servlet:
multipart:
max-file-size: 512000 #请求文件大小限制为500kb
max-request-size: 512000
service-api com/imooc/api/config/InterceptorConfig.java
此时拦截器也要加一层
package com.imooc.api.config;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UseActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UseActiveInterceptor useActiveInterceptor(){
return new UseActiveInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace");
// registry.addInterceptor(userTokenInterceptor())
// .addPathPatterns("/user/getAccountInfo")
}
}
图片大小控制的统一异常处理
dev-common com/imooc/exception/GraceExceptionHandler.java
package com.imooc.exception;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
/**
* 统一异常拦截处理
* 可以针对异常的类型进行捕获 然后返回json信息到前端
*/
@ControllerAdvice
public class GraceExceptionHandler {
@ExceptionHandler(MyCustomException.class)
//只要是这个类的异常都会进入下面的方法
@ResponseBody
public GraceJSONResult returnMyException(MyCustomException e){
e.printStackTrace(); //打印信息
return GraceJSONResult.exception(e.getResponseStatusEnum());
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseBody
public GraceJSONResult returnMaxUploadSizeExceededException(MaxUploadSizeExceededException e){
e.printStackTrace(); //打印信息
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_MAX_SIZE_ERROR);
}
}
第三方云存储解决方案 【阿里OSS】
FastDFS [中小型公司使用]
- 水平扩容
- 运维复杂
- 开发复杂【增加图片效果 & 人脸识别等】
云存储阿里OSS
- SDK使用简单 [Java对接]
- 提供强大的文件处理功能
- 零运维成本
- 图形化管理控制台
- CDN加速
- 降低风险管理成本
对象存储 OSS 资源包 (aliyun.com)[购买 标准-本地冗余存储 + 下行流量 ]
控制台的基本配置使用 【阿里OSS】
- 对象存储OSS → Bucket列表 → 创建Bucket → 存储冗余类型:本地冗余存储 → 读写权限:公共读
- 创建成功后进入iimooc-news-dev/object → 文件管理 → 文件列表 →
https://iimooc-news-dev.oss-cn-shanghai.aliyuncs.com/log.png
dev-common pom.xml
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
- 对象存储OSS → SDK文档 → 上传网络流
- 对象存储OSS → iimooc-news-dev → 概览 → 访问端口: 外网访问 oss-cn-shanghai.aliyuncs.com
SDK的使用与项目整合
service-file com/imooc/files/service/UploaderService.java
package com.imooc.files.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface UploaderService {
/**
* 使用fastdfs 上传文件
*/
public String uploadFdfs(MultipartFile file, String fileExtName) throws IOException;
/**
* 使用OSS 上传文件
*/
public String uploadOSS(MultipartFile file,String userId, String fileExtName) throws IOException;
}
service-files com/imooc/files/service/impl/UploaderServiceImpl.java
package com.imooc.files.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.imooc.files.resource.FileResource;
import com.imooc.files.service.UploaderService;
import com.imooc.utils.extend.AliyunResource;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
@Service
public class UploaderServiceImpl implements UploaderService {
//注入客户端
@Autowired
public FastFileStorageClient fastFileStorageClient;
@Autowired
public FileResource fileResource;
@Autowired
public AliyunResource aliyunResource;
@Autowired
public Sid sid;
@Override
public String uploadFdfs(MultipartFile file, String fileExtName) throws IOException {
StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), fileExtName, null);
return storePath.getFullPath();
}
@Override
public String uploadOSS(MultipartFile file, String userId, String fileExtName) throws IOException {
// Endpoint以杭州为例,其它Region请按实际情况填写。
// 外网访问:oss-cn-shanghai.aliyuncs.com
String endpoint = fileResource.getEndpoint();
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = aliyunResource.getAccessKeyID();
String accessKeySecret = aliyunResource.getAccessKeySecret();
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint,
accessKeyId,
accessKeySecret);
// images/abc/10010/cat.png 路径不全 所以需要myObjectName拼接
String fileName = sid.nextShort();
String myObjectName = fileResource.getObjectName()
+ "/" + userId + "/" + fileName + "." + fileExtName;
// 上传网络流。
InputStream inputStream = file.getInputStream();
ossClient.putObject(fileResource.getBucketName(),
myObjectName,
inputStream);
// 关闭OSSClient。
ossClient.shutdown();
return myObjectName;
}
}
service-files com/imooc/files/resource/FileResource.java
package com.imooc.files.resource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:file-${spring.profiles.active}.properties ") //这个是在application.yml里面的 自动匹配
@ConfigurationProperties(prefix = "file")
public class FileResource {
private String host;
private String endpoint;
private String BucketName;
private String objectName;
}Getter + Setter
file-dev.properties
# fastdfs storage ????(nginx???web??)
file.host=http://192.168.170.136:8888/
# aliyun OSS
file.endpoint=oss-cn-shanghai.aliyuncs.com
file.BucketName=iimooc-news-dev
# url name
file.objectName=images/abc
OSS整合实现文件上传
// OSS执行上传
// path = uploaderService.uploadOSS(file, userId, suffix);
service-files com/imooc/files/controller/FileUploadController.java
package com.imooc.files.controller;
import com.imooc.api.controller.files.FileUploadControllerApi;
import com.imooc.files.resource.FileResource;
import com.imooc.files.service.UploaderService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController implements FileUploadControllerApi {
final static Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@Autowired
private UploaderService uploaderService;
@Autowired
private FileResource fileResource;
@Override
public GraceJSONResult uploadFace(String userId,
MultipartFile file) throws Exception {
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_FORMATTER_FAILD);
}
// fdfs执行上传
// path = uploaderService.uploadFdfs(file, suffix);
// OSS执行上传
path = uploaderService.uploadOSS(file, userId, suffix);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
logger.info("path = " + path);
String finalPath = "";
if (StringUtils.isNotBlank(path)){
finalPath = fileResource.getHost() + path;
} else{
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
return GraceJSONResult.ok(finalPath);
}
}
=================================================================================
http://writer.imoocnews.com:9090/imooc-news/writer/accountInfo.html
此时更改头像上传后 头像会在OSS服务器的文件里面显示
file-dev.properties / file-prod.properties
# fastdfs storage ????(nginx???web??)
file.host=http://192.168.170.136:8888/
# aliyun OSS
file.endpoint=oss-cn-shanghai.aliyuncs.com
file.BucketName=iimooc-news-dev
# url name
file.objectName=images/abc
file.ossHost=https://iimooc-news-dev.oss-cn-shanghai.aliyuncs.com/
service-files com/imooc/files/resource/FileResource.java
@Component
@PropertySource("classpath:file-${spring.profiles.active}.properties ") //这个是在application.yml里面的 自动匹配
@ConfigurationProperties(prefix = "file")
public class FileResource {
private String host;
private String endpoint;
private String BucketName;
private String objectName;
private String OssHost;
}Getter + Setter
service-files com/imooc/files/controller/FileUploadController.java
//用OSS执行上传 而不是 fdfs执行上传
package com.imooc.files.controller;
import com.imooc.api.controller.files.FileUploadControllerApi;
import com.imooc.files.resource.FileResource;
import com.imooc.files.service.UploaderService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController implements FileUploadControllerApi {
final static Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@Autowired
private UploaderService uploaderService;
@Autowired
private FileResource fileResource;
@Override
public GraceJSONResult uploadFace(String userId,
MultipartFile file) throws Exception {
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_FORMATTER_FAILD);
}
// fdfs执行上传
// path = uploaderService.uploadFdfs(file, suffix);
// OSS执行上传
path = uploaderService.uploadOSS(file, userId, suffix);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
logger.info("path = " + path);
String finalPath = "";
if (StringUtils.isNotBlank(path)){
// finalPath = fileResource.getHost() + path;
finalPath = fileResource.getOssHost() + path;
} else{
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
return GraceJSONResult.ok(finalPath);
}
}
图片自动审核 【阿里内容安全】
多媒体内容风险智能识别服务,降低色情、暴力、恐怖 (由于太贵了就不买了 1000多呢)
dev-common pom.xml
<!-- 第三方云厂商相关的依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-green</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
dev-common com/imooc/utils/extend/AliImageReviewUtils.java
package com.imooc.utils.extend;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.imooc.enums.ArticleReviewLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
@Component
public class AliImageReviewUtils {
// 文档地址:https://help.aliyun.com/document_detail/70292.html?spm=a2c4g.11186623.2.49.6f9c75fdjaW30p#reference-fzy-ztm-v2b
@Autowired
private AliyunResource aliyunResource;
public boolean reviewImage(String imgUrl) throws Exception {
IClientProfile profile = DefaultProfile
.getProfile("cn-shanghai", "", "");
DefaultProfile
.addEndpoint("cn-shanghai", "cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
IAcsClient client = new DefaultAcsClient(profile);
ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
// 指定api返回格式
imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
// 指定请求方法
imageSyncScanRequest.setMethod(MethodType.POST);
imageSyncScanRequest.setEncoding("utf-8");
//支持http和https
imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
JSONObject httpBody = new JSONObject();
/**
* 设置要检测的场景, 计费是按照该处传递的场景进行
* 一次请求中可以同时检测多张图片,每张图片可以同时检测多个风险场景,计费按照场景计算
* 例如:检测2张图片,场景传递porn、terrorism,计费会按照2张图片鉴黄,2张图片暴恐检测计算
* porn: porn表示色情场景检测
* logo: 商标
* 其他详见官方文档
*/
httpBody.put("scenes", Arrays.asList("terrorism"));
/**
* 设置待检测图片, 一张图片一个task
* 多张图片同时检测时,处理的时间由最后一个处理完的图片决定
* 通常情况下批量检测的平均rt比单张检测的要长, 一次批量提交的图片数越多,rt被拉长的概率越高
* 这里以单张图片检测作为示例, 如果是批量图片检测,请自行构建多个task
*/
JSONObject task = new JSONObject();
task.put("dataId", UUID.randomUUID().toString());
//设置图片链接
task.put("url", imgUrl);
task.put("time", new Date());
httpBody.put("tasks", Arrays.asList(task));
imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
"UTF-8", FormatType.JSON);
/**
* 请设置超时时间, 服务端全链路处理超时时间为10秒,请做相应设置
* 如果您设置的ReadTimeout小于服务端处理的时间,程序中会获得一个read timeout异常
*/
imageSyncScanRequest.setConnectTimeout(3000);
imageSyncScanRequest.setReadTimeout(10000);
HttpResponse httpResponse = null;
try {
httpResponse = client.doAction(imageSyncScanRequest);
} catch (Exception e) {
e.printStackTrace();
}
//服务端接收到请求,并完成处理返回的结果
if (httpResponse != null && httpResponse.isSuccess()) {
JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
System.out.println(JSON.toJSONString(scrResponse, true));
int requestCode = scrResponse.getIntValue("code");
//每一张图片的检测结果
JSONArray taskResults = scrResponse.getJSONArray("data");
if (200 == requestCode) {
for (Object taskResult : taskResults) {
//单张图片的处理结果
int taskCode = ((JSONObject) taskResult).getIntValue("code");
//图片要检测的场景的处理结果, 如果是多个场景,则会有每个场景的结果
JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
if (200 == taskCode) {
Object sceneResult = sceneResults.get(0);
// for (Object sceneResult : sceneResults) {
String scene = ((JSONObject) sceneResult).getString("scene");
String suggestion = ((JSONObject) sceneResult).getString("suggestion");
//根据scene和suggetion做相关处理
//do something
System.out.println("scene = [" + scene + "]");
System.out.println("suggestion = [" + suggestion + "]");
return suggestion.equalsIgnoreCase(ArticleReviewLevel.PASS.type) ? true : false;
// }
} else {
//单张图片处理失败, 原因视具体的情况详细分析
System.out.println("task process fail. task response:" + JSON.toJSONString(taskResult));
return false;
}
}
} else {
/**
* 表明请求整体处理失败,原因视具体的情况详细分析
*/
System.out.println("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
return false;
}
}
return false;
}
}
dev-common com/imooc/enums/ArticleReviewLevel.java
package com.imooc.enums;
/**
* @Desc: 文章自动审核结果 枚举
*/
public enum ArticleReviewLevel {
PASS("pass", "自动审核通过"),
BLOCK("block", "自动审核不通过"),
REVIEW("review", "建议人工复审");
public final String type;
public final String value;
ArticleReviewLevel(String type, String value) {
this.type = type;
this.value = value;
}
}
service-files com/imooc/files/controller/FileUploadController.java
package com.imooc.files.controller;
import com.imooc.api.controller.files.FileUploadControllerApi;
import com.imooc.files.resource.FileResource;
import com.imooc.files.service.UploaderService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.extend.AliImageReviewUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController implements FileUploadControllerApi {
final static Logger logger = LoggerFactory.getLogger(FileUploadController.class);
@Autowired
private UploaderService uploaderService;
@Autowired
private FileResource fileResource;
@Autowired
private AliImageReviewUtils aliImageReviewUtils;
@Override
public GraceJSONResult uploadFace(String userId,
MultipartFile file) throws Exception {
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_FORMATTER_FAILD);
}
// fdfs执行上传 要让外面得以访问 ①需要把内网的环境发布到公网 [内网穿透] ②路由器端口映射到外网 ③fastdfs安装到公网里
// path = uploaderService.uploadFdfs(file, suffix);
// OSS执行上传
path = uploaderService.uploadOSS(file, userId, suffix);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
}
logger.info("path = " + path);
String finalPath = "";
if (StringUtils.isNotBlank(path)){
// finalPath = fileResource.getHost() + path;
finalPath = fileResource.getOssHost() + path;
} else{
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_UPLOAD_FAILD);
}
return GraceJSONResult.ok(finalPath);
// return GraceJSONResult.ok(doAliImageReview(finalPath)); //这里加了图片审核咯
}
/**
* fastdfs 默认存在于内网,无法被阿里云内容管理服务检查到
* 需要配置到公网才行:
* 1. 内网穿透,natppp/花生壳/ngrok
* 2. 路由配置端口映射
* 3. fdfs 发布到云服务器
*/
/* 功能实现不了图片识别 因为没有开通内容安全需要企业认证
public static final String FAILED_IMAGE_URL = "https://iimooc-news-dev.oss-cn-shanghai.aliyuncs.com/images/abc/240629F21AK1BHX4/Review_Failed.png"; //这里保存审核失败的照片 提前上传到Oss里直接用
private String doAliImageReview(String pendingImageUrl){
boolean result = false;
try {
result = aliImageReviewUtils.reviewImage(pendingImageUrl);
} catch (Exception e) {
e.printStackTrace();
}
if (!result){
return FAILED_IMAGE_URL;
}
return pendingImageUrl;
}
}
*/
创建阿里云子账号 【阿里内容安全】
RAM访问控制 → 用户
[实在不行了 凑合着搞一下吧 功能实现不了图片识别 因为没有开通内容安全需要企业认证]
登录名称:imooc-news-dev
显示名称:用于内容审核
√ OpenAPI访问调用
AccessKey ID:
AccessKeySeret:点击左侧列表下方 授权 → 新增授权 【授权主体:用于内容审核 权限策略:搜:green … 管理内容安全的权限】
构建admin服务
- 构建admin管理服务
- 文章分类管理
- 友情连接管理
- 用户账号管理
- 文章内容人工审核 [放在文章上传后的自动审核]
- admin管理人员账号分配 [用户人脸]
service-admin 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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>imooc-news-dev-service-admin</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
service-admin logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<!-- <property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/service-admin"/>-->
<property name="LOG_HOME" value="C:/Users/Pluminary/Desktop/imooc-news-admin"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/service-admin.%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>
<!--<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--</logger>-->
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
service-admin:8005
package com.imooc.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.user.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
service-admin com/imooc/admin/controller/HelloController.java
package com.imooc.admin.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.RedisOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello() {
return GraceJSONResult.ok();
}
}
----------------------------------------------------------------------------
http://admin.imoocnews.com:8005/hello
application-dev.yml
server:
port: 8005
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
表设计与账号预分配 【admin账号】
service-admin pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
service-admin com/imooc/admin/controller/PWDTest.java
package com.imooc.admin.controller;
import org.springframework.security.crypto.bcrypt.BCrypt;
public class PWDTest {
public static void main(String[] args) {
String pwd = BCrypt.hashpw("admin", BCrypt.gensalt());//加盐
System.out.println(pwd);
}
}
持久层查询管理员 【admin账号】
http://admin.imoocnews.com:9090/imooc-news/admin/login.html
Windows下如何查看某个端口被谁占用 | 菜鸟教程 (runoob.com)
//更改一下mybatis-generator-database里面的generatorConfig-admin.xml
数据库表为:admin_user
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 通用mapper所在目录 -->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.imooc.my.mapper.MyMapper"/>
</plugin>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/imooc-news-dev"
userId="root"
password="root">
</jdbcConnection>
<!-- 对应生成的pojo所在包 -->
<javaModelGenerator targetPackage="com.imooc.pojo" targetProject="mybatis-generator-database/src/main/java"/>
<!-- 对应生成的mapper所在目录 -->
<sqlMapGenerator targetPackage="mapper.admin" targetProject="mybatis-generator-database/src/main/resources"/>
<!-- 配置mapper对应的java映射 -->
<javaClientGenerator targetPackage="com.imooc.admin.mapper" targetProject="mybatis-generator-database/src/main/java" type="XMLMAPPER"/>
<!-- 数据库表 -->
<table tableName="admin_user"></table>
</context>
</generatorConfiguration>
mybatis-generator-database的把AdminUser拷贝到dev-model的com/imooc/pojo下
mybatis-generator-database的把AdminUserMapper拷贝到service-admin的resources mapper/AdminUserMapper.xml
service-admin com/imooc/admin/service/AdminUserService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
public interface AdminUserService {
/**
* 获得管理员的用户信息
* @param username
* @return
*/
public AdminUser queryAdminByUsername(String username);
}
service-admin com/imooc/admin/service/impl/AdminUserServiceImpl.java
package com.imooc.admin.service.impl;
import com.imooc.admin.mapper.AdminUserMapper;
import com.imooc.admin.service.AdminUserService;
import com.imooc.pojo.AdminUser;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.entity.Example;
public class AdminUserServiceImpl implements AdminUserService {
@Autowired
public AdminUserMapper adminUserMapper;
@Override
public AdminUser queryAdminByUsername(String username) {
Example adminExample = new Example(AdminUser.class);
Example.Criteria Criteria = adminExample.createCriteria();
Criteria.andEqualTo("username",username);
AdminUser admin = adminUserMapper.selectOneByExample(adminExample);
return admin;
}
}
service-admin com/imooc/admin/mapper/AdminUserMapper.java
package com.imooc.admin.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.AdminUser;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminUserMapper extends MyMapper<AdminUser> {
}
service-admin mapper/AdminUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.admin.mapper.AdminUserMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.AdminUser" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="face_id" property="faceId" jdbcType="VARCHAR" />
<result column="admin_name" property="adminName" jdbcType="VARCHAR" />
<result column="created_time" property="createdTime" jdbcType="TIMESTAMP" />
<result column="updated_time" property="updatedTime" jdbcType="TIMESTAMP" />
</resultMap>
</mapper>
用户名密码登录 【admin账号】
Spring里遇到的一个问题,autowired时报找不到bean定义_autowired找不到bean-CSDN博客
其次上述问题一定要去找Controller Service ServiceImpl 和 启动类里面的有没有正确扫描包@MapperScan(basePackages = “com.imooc.admin.mapper”)
service-admin com/imooc/admin/controller/AdminMngController.java
package com.imooc.admin.controller;
import com.imooc.admin.service.AdminUserService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.AdminMngControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.utils.RedisOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@RestController
public class AdminMngController extends BaseController implements AdminMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(AdminMngController.class);
@Autowired
private AdminUserService adminUserService;
@Autowired
private RedisOperator redis;
@Override
public Object adminLogin(AdminLoginBO adminLoginBO, HttpServletRequest request, HttpServletResponse response) {
// 0. TODO 验证BO中的用户名和密码不为空
// 1.查询admin用户的信息
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
// 2.判断admin不为空,如果为空则登录失败
if (admin == null) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
// 3.判断密码是否匹配
boolean isPwdMath = BCrypt.checkpw(adminLoginBO.getPassword(), admin.getPassword());
if (isPwdMath){
doLoginSettings(admin,request,response);
return GraceJSONResult.ok();
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
}
/**
* 用于admin用户登录过后的基本信息设置
*/
private void doLoginSettings(AdminUser admin, HttpServletRequest request, HttpServletResponse response){
// 保存token放入到redis中
String token = UUID.randomUUID().toString();
redis.set(REDIS_ADMIN_TOKEN + ":" + admin.getId(),token);
// 保存admin登录基本token信息到cookie中
setCookie(request, response, "atoken", token, COOKIE_MONTH);
setCookie(request, response, "aid", admin.getId(), COOKIE_MONTH);
setCookie(request, response, "aname", admin.getAdminName(), COOKIE_MONTH);
}
}
---------------------------------------------------------------------------------
http://admin.imoocnews.com:9090/imooc-news/admin/login.html
service-admin com/imooc/admin/service/AdminUserService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
public interface AdminUserService {
/**
* 获得管理员的用户信息
* @param username
* @return
*/
public AdminUser queryAdminByUsername(String username);
}
service-admin com/imooc/admin/service/impl/AdminUserServiceImpl.java
package com.imooc.admin.service.impl;
import com.imooc.admin.mapper.AdminUserMapper;
import com.imooc.admin.service.AdminUserService;
import com.imooc.pojo.AdminUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
@Service
public class AdminUserServiceImpl implements AdminUserService {
@Autowired
public AdminUserMapper adminUserMapper;
@Override
public AdminUser queryAdminByUsername(String username) {
Example adminExample = new Example(AdminUser.class);
Example.Criteria Criteria = adminExample.createCriteria();
Criteria.andEqualTo("username",username);
AdminUser admin = adminUserMapper.selectOneByExample(adminExample);
return admin;
}
}
service-admin com/imooc/admin/mapper/AdminUserMapper.java
package com.imooc.admin.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.AdminUser;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminUserMapper extends MyMapper<AdminUser> {
}
service-admin resources/mapper/AdminUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.admin.mapper.AdminUserMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.AdminUser" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="face_id" property="faceId" jdbcType="VARCHAR" />
<result column="admin_name" property="adminName" jdbcType="VARCHAR" />
<result column="created_time" property="createdTime" jdbcType="TIMESTAMP" />
<result column="updated_time" property="updatedTime" jdbcType="TIMESTAMP" />
</resultMap>
</mapper>
service-admin com/imooc/admin/Application.java
package com.imooc.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.admin.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
service-admin application-dev.yml
server:
port: 8005
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
## setup CN from java, This is resource
website:
domain-name: imoocnews.com
## open mybatis log in dev
#mybatis:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#
校验admin账号唯一 【admin账号】
http://admin.imoocnews.com:9090/imooc-news/admin/adminMng.html
service-admin com/imooc/admin/controller/AdminMngController.java
@Override
public Object adminLogin(String username) {
checkAdminExist(username);
return GraceJSONResult.ok();
}
private void checkAdminExist(String username){
AdminUser admin = adminUserService.queryAdminByUsername(username);
if (admin != null){
GraceException.display(ResponseStatusEnum.ADMIN_USERNAME_EXIST_ERROR);
}
}
service-api com/imooc/api/controller/admin/AdminMngControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.pojo.bo.AdminLoginBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "管理员admin维护",tags = {"管理员admin维护的Controller"})
@RequestMapping("adminMng")
public interface AdminMngControllerApi {
@ApiOperation(value = "hello方法的接口",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/adminLogin")
public Object adminLogin(@RequestBody AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response);
@ApiOperation(value = "查询admin用户名是否存在",notes = "查询admin用户名是否存在",httpMethod = "POST")
@PostMapping("/adminIsExist")
public Object adminLogin(@RequestParam String username); //传回来
}
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UseActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UseActiveInterceptor useActiveInterceptor(){
return new UseActiveInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace");
// registry.addInterceptor(userTokenInterceptor())
// .addPathPatterns("/user/getAccountInfo")
}
}
创建admin账号 【admin账号】
用户管理 | 运营管理平台 (imoocnews.com)
http://admin.imoocnews.com:9090/imooc-news/admin/adminMng.html
service-admin com/imooc/admin/controller/AdminMngController.java
package com.imooc.admin.controller;
import com.imooc.admin.service.AdminUserService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.AdminMngControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@RestController
public class AdminMngController extends BaseController implements AdminMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(AdminMngController.class);
@Autowired
private AdminUserService adminUserService;
@Autowired
private RedisOperator redis;
@Override
public GraceJSONResult adminLogin(AdminLoginBO adminLoginBO, HttpServletRequest request, HttpServletResponse response) {
// 0. TODO 验证BO中的用户名和密码不为空
// 1.查询admin用户的信息
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
// 2.判断admin不为空,如果为空则登录失败
if (admin == null) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
// 3.判断密码是否匹配
boolean isPwdMath = BCrypt.checkpw(adminLoginBO.getPassword(), admin.getPassword());
if (isPwdMath){
doLoginSettings(admin,request,response);
return GraceJSONResult.ok();
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
}
/**
* 用于admin用户登录过后的基本信息设置
*/
private void doLoginSettings(AdminUser admin, HttpServletRequest request, HttpServletResponse response){
// 保存token放入到redis中
String token = UUID.randomUUID().toString();
redis.set(REDIS_ADMIN_TOKEN + ":" + admin.getId(),token);
// 保存admin登录基本token信息到cookie中
setCookie(request, response, "atoken", token, COOKIE_MONTH);
setCookie(request, response, "aid", admin.getId(), COOKIE_MONTH);
setCookie(request, response, "aname", admin.getAdminName(), COOKIE_MONTH);
}
@Override
public GraceJSONResult adminLogin(String username) {
checkAdminExist(username);
return GraceJSONResult.ok();
}
private void checkAdminExist(String username){
AdminUser admin = adminUserService.queryAdminByUsername(username);
if (admin != null){
GraceException.display(ResponseStatusEnum.ADMIN_USERNAME_EXIST_ERROR);
}
}
@Override
public GraceJSONResult addNewAdmin(NewAdminBO newAdminBO,HttpServletRequest request,HttpServletResponse response) {
// 0. TODO 验证BO中的用户名和密码不为空
// 1. base64不为空,则代表人脸入库,否则需要用户输入密码和确认密码
if (StringUtils.isBlank(newAdminBO.getImg64())){
if (StringUtils.isBlank(newAdminBO.getPassword()) || StringUtils.isBlank(newAdminBO.getConfirmPassword())){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_PASSWORD_NULL_ERROR);
}
}
// 2. 密码不为空,则必须判断两次输入一致
if (StringUtils.isNotBlank(newAdminBO.getPassword())) {
if (!newAdminBO.getPassword().equalsIgnoreCase(newAdminBO.getConfirmPassword())) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_PASSWORD_ERROR);
}
}
// 3. 校验用户名唯一
checkAdminExist(newAdminBO.getUsername());
// 4.调用service存入admin信息
adminUserService.createAdminUser(newAdminBO);
return GraceJSONResult.ok();
}
}
service-api com/imooc/api/controller/admin/AdminMngControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "管理员admin维护",tags = {"管理员admin维护的Controller"})
@RequestMapping("adminMng")
public interface AdminMngControllerApi {
@ApiOperation(value = "hello方法的接口",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/adminLogin")
public GraceJSONResult adminLogin(@RequestBody AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response);
@ApiOperation(value = "查询admin用户名是否存在",notes = "查询admin用户名是否存在",httpMethod = "POST")
@PostMapping("/adminIsExist")
public GraceJSONResult adminLogin(@RequestParam String username); //传回来
@ApiOperation(value = "创建admin",notes = "创建admin",httpMethod = "POST")
@PostMapping("/addNewAdmin")
public GraceJSONResult addNewAdmin(@RequestBody NewAdminBO newAdminBO,HttpServletRequest request,HttpServletResponse response); //传回来
}
service-admin com/imooc/admin/service/impl/AdminUserServiceImpl.java
package com.imooc.admin.service.impl;
import com.imooc.admin.mapper.AdminUserMapper;
import com.imooc.admin.service.AdminUserService;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import org.apache.commons.lang3.StringUtils;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
@Service
public class AdminUserServiceImpl implements AdminUserService {
@Autowired
public AdminUserMapper adminUserMapper;
@Autowired
public Sid sid;
@Override
public AdminUser queryAdminByUsername(String username) {
Example adminExample = new Example(AdminUser.class);
Example.Criteria Criteria = adminExample.createCriteria();
Criteria.andEqualTo("username",username);
AdminUser admin = adminUserMapper.selectOneByExample(adminExample);
return admin;
}
@Override
public void createAdminUser(NewAdminBO newAdminBO) {
String adminId = sid.nextShort(); //获得主键
AdminUser adminUser = new AdminUser();
adminUser.setId(adminId);
adminUser.setUsername(newAdminBO.getUsername());
adminUser.setAdminName(newAdminBO.getAdminName());
// 如果密码不为空 则密码需要加密 存入数据库
if (StringUtils.isNotBlank(newAdminBO.getPassword())){
String pwd = BCrypt.hashpw(newAdminBO.getPassword(), BCrypt.gensalt());
adminUser.setPassword(pwd);
}
// 如果人脸上传以后,则有faceId,需要和admin信息关联存储入库
if (StringUtils.isNotBlank(newAdminBO.getFaceId())){
adminUser.setFaceId(newAdminBO.getFaceId());
}
adminUser.setCreatedTime(new Date());
adminUser.setUpdatedTime(new Date());
int insert = adminUserMapper.insert(adminUser);
if (insert != 1){
GraceException.display(ResponseStatusEnum.ADMIN_CREATE_ERROR);
}
}
}
com/imooc/admin/service/AdminUserService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
public interface AdminUserService {
/**
* 获得管理员的用户信息
* @param username
* @return
*/
public AdminUser queryAdminByUsername(String username);
/**
* 新增管理员
*
* @param newAdminBO
*/
public void createAdminUser(NewAdminBO newAdminBO);
}
查看admin列表 【admin账号】(分页查询)
service-admin com/imooc/admin/controller/AdminMngController.java
@Override
public GraceJSONResult getAdminList(Integer page, Integer pageSize) {
if (page == null){
page = COMMON_START_PAGE;
}
if (pageSize == null){//由于是固定数值 可以去basecontroller加一下
pageSize = COMMON_PAGE_SIZE;
}
adminUserService.queryAdminList(page, pageSize);
return GraceJSONResult.ok();
}
service-admin com/imooc/admin/service/AdminUserService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
public interface AdminUserService {
/**
* 获得管理员的用户信息
* @param username
* @return
*/
public AdminUser queryAdminByUsername(String username);
/**
* 新增管理员
*
* @param newAdminBO
*/
public void createAdminUser(NewAdminBO newAdminBO);
/**
* 分页查询admin列表
* @param page
* @param pageSize
*/
public void queryAdminList(Integer page, Integer pageSize);
}
service-admin com/imooc/admin/service/impl/AdminUserServiceImpl.java
@Override
public void queryAdminList(Integer page, Integer pageSize) {
Example adminExample = new Example(AdminUser.class);
adminExample.orderBy("createdTime").asc();
PageHelper.startPage(page, pageSize);
List<AdminUser> adminUserList = adminUserMapper.selectByExample(adminExample);
System.out.println(adminUserList);
} //下面一节会有改动
service-api com/imooc/api/config/Swagger2.java
package com.imooc.api.config;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration //Springboot啓動的時候會被掃描到并且加載
@EnableSwagger2
public class Swagger2 {
// http://localhost:8088/swagger-ui.html 原路径
// http://localhost:8088/doc.html 新路径
// 配置swagger2核心配置 docket
@Bean
public Docket createRestApi() {
Predicate<RequestHandler> adminPredicate = RequestHandlerSelectors.basePackage("com.imooc.admin.controller");
// Predicate<RequestHandler> articlePredicate = RequestHandlerSelectors.basePackage("com.imooc.article.controller");
Predicate<RequestHandler> userPredicate = RequestHandlerSelectors.basePackage("com.imooc.user.controller");
Predicate<RequestHandler> filesPredicate = RequestHandlerSelectors.basePackage("com.imooc.files.controller");
return new Docket(DocumentationType.SWAGGER_2) // 指定api类型为swagger2
.apiInfo(apiInfo()) // 用于定义api文档汇总信息
.select()
.apis(Predicates.or(userPredicate, adminPredicate, filesPredicate))
// .apis(Predicates.or(adminPredicate, articlePredicate, userPredicate, filesPredicate))
.paths(PathSelectors.any()) // 所有controller
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("慕课新闻·自媒体接口api") // 文档页标题
.contact(new Contact("imooc",
"https://www.imooc.com",
"abc@imooc.com")) // 联系人信息
.description("专为慕课新闻·自媒体平台提供的api文档") // 详细信息
.version("1.0.1") // 文档版本号
.termsOfServiceUrl("https://www.imooc.com") // 网站地址
.build();
}
}
service-api com/imooc/api/controller/admin/AdminMngControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "管理员admin维护",tags = {"管理员admin维护的Controller"})
@RequestMapping("adminMng")
public interface AdminMngControllerApi {
@ApiOperation(value = "hello方法的接口",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/adminLogin")
public GraceJSONResult adminLogin(@RequestBody AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response);
@ApiOperation(value = "查询admin用户名是否存在",notes = "查询admin用户名是否存在",httpMethod = "POST")
@PostMapping("/adminIsExist")
public GraceJSONResult adminLogin(@RequestParam String username); //传回来
@ApiOperation(value = "创建admin",notes = "创建admin",httpMethod = "POST")
@PostMapping("/addNewAdmin")
public GraceJSONResult addNewAdmin(@RequestBody NewAdminBO newAdminBO,HttpServletRequest request,HttpServletResponse response);
@ApiOperation(value = "查询admin列表",notes = "查询admin列表",httpMethod = "POST")
@PostMapping("/getAdminList")
public GraceJSONResult getAdminList(@ApiParam(name = "page", value = "查询下一页的第几页", required = false) @RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页查询每一页显示的条数", required = false) @RequestParam Integer pageSize);
}
-----------------------------------------------------------------
http://admin.imoocnews.com:8005/doc.html
封装PagedGridResult分页数据_调试分页接口 【admin账号】
service-admin com/imooc/admin/service/impl/AdminUserServiceImpl.java
@Override
public PagedGridResult queryAdminList(Integer page, Integer pageSize) {
Example adminExample = new Example(AdminUser.class);
adminExample.orderBy("createdTime").asc();
PageHelper.startPage(page, pageSize);
List<AdminUser> adminUserList = adminUserMapper.selectByExample(adminExample);
return setterPagedGrid(adminUserList, page);
}
private PagedGridResult setterPagedGrid( List<?> adminUserList, Integer page){ //类型是? 后期不确定是什么泛型
PageInfo<?> pageList = new PageInfo<>(adminUserList);
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(adminUserList);
gridResult.setPage(page);
gridResult.setRecords(pageList.getPages());
gridResult.setTotal(pageList.getTotal());
return gridResult;
}
service-admin com/imooc/admin/service/AdminUserService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.utils.PagedGridResult;
public interface AdminUserService {
/**
* 获得管理员的用户信息
* @param username
* @return
*/
public AdminUser queryAdminByUsername(String username);
/**
* 新增管理员
*
* @param newAdminBO
*/
public void createAdminUser(NewAdminBO newAdminBO);
/**
* 分页查询admin列表
* @param page
* @param pageSize
*/
public PagedGridResult queryAdminList(Integer page, Integer pageSize);
}
dev-common com/imooc/utils/PagedGridResult.java
package com.imooc.utils;
import java.util.List;
/**
*
* @Title: PagedGridResult.java
* @Package com.imooc.utils
* @Description: 用来返回分页Grid的数据格式
* Copyright: Copyright (c) 2019
*/
public class PagedGridResult {
private int page; // 当前页数
private long total; // 总页数
private long records; // 总记录数
private List<?> rows; // 每行显示的内容
}Getter + Setter
service-api com/imooc/api/config/InterceptorConfig.java
//拦截器新增地址
package com.imooc.api.config;
import com.imooc.api.interceptors.AdminTokenInterceptor;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UserActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UserActiveInterceptor userActiveInterceptor() {
return new UserActiveInterceptor();
}
@Bean
public AdminTokenInterceptor adminTokenInterceptor() {
return new AdminTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace");
registry.addInterceptor(adminTokenInterceptor())//继续添加拦截器:查询admin列表 创建新admin用户
.addPathPatterns("/adminMng/adminIsExist")
.addPathPatterns("/adminMng/addNewAdmin")
.addPathPatterns("/adminMng/getAdminList");
}
}
service-api com/imooc/api/interceptors/AdminTokenInterceptor.java
package com.imooc.api.interceptors;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static com.imooc.api.BaseController.REDIS_ADMIN_TOKEN;
public class AdminTokenInterceptor extends BaseInterceptor implements HandlerInterceptor {
/**
* 拦截请求,在访问controller调用之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String adminUserId = request.getHeader("adminUserId");
String adminUserToken = request.getHeader("adminUserToken");
System.out.println("=====================================================================");
System.out.println("AdminTokenInterceptor - adminUserId = " + adminUserId);
System.out.println("AdminTokenInterceptor - adminUserToken = " + adminUserToken);
System.out.println("=====================================================================");
boolean run = verifyUserIdToken(adminUserId, adminUserToken, REDIS_ADMIN_TOKEN);
return run;
}
/**
* 请求访问controller之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 请求访问controller之后,渲染视图之后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
账号注销 【admin账号】(删掉redis和cookie数据)
service-admin com/imooc/admin/controller/AdminMngController.java
package com.imooc.admin.controller;
import com.imooc.admin.service.AdminUserService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.AdminMngControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.enums.FaceVerifyType;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.utils.FaceVerifyUtils;
import com.imooc.utils.PagedGridResult;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@RestController
public class AdminMngController extends BaseController implements AdminMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(AdminMngController.class);
@Autowired
private RedisOperator redis;
@Autowired
private AdminUserService adminUserService;
@Autowired
private FaceVerifyUtils faceVerifyUtils;
@Override
public GraceJSONResult adminLogin(AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response) {
// 0. TODO 验证BO中的用户名和密码不为空
// 1. 查询admin用户的信息
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
// 2. 判断admin不为空,如果为空则登录失败
if (admin == null) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
// 3. 判断密码是否匹配
boolean isPwdMatch = BCrypt.checkpw(adminLoginBO.getPassword(), admin.getPassword());
if (isPwdMatch) {
doLoginSettings(admin, request, response);
return GraceJSONResult.ok();
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_NOT_EXIT_ERROR);
}
}
/**
* 用于admin用户登录过后的基本信息设置
* @param admin
* @param request
* @param response
*/
private void doLoginSettings(AdminUser admin,
HttpServletRequest request,
HttpServletResponse response) {
// 保存token放入到redis中
String token = UUID.randomUUID().toString();
redis.set(REDIS_ADMIN_TOKEN + ":" + admin.getId(), token);
// 保存admin登录基本token信息到cookie中
setCookie(request, response, "atoken", token, COOKIE_MONTH);
setCookie(request, response, "aid", admin.getId(), COOKIE_MONTH);
setCookie(request, response, "aname", admin.getAdminName(), COOKIE_MONTH);
}
@Override
public GraceJSONResult adminIsExist(String username) {
checkAdminExist(username);
return GraceJSONResult.ok();
}
private void checkAdminExist(String username) {
AdminUser admin = adminUserService.queryAdminByUsername(username);
if (admin != null) {
GraceException.display(ResponseStatusEnum.ADMIN_USERNAME_EXIST_ERROR);
}
}
@Override
public GraceJSONResult addNewAdmin(NewAdminBO newAdminBO,
HttpServletRequest request,
HttpServletResponse response) {
// 0. TODO 验证BO中的用户名和密码不为空
// 1. base64不为空,则代表人脸入库,否则需要用户输入密码和确认密码
if (StringUtils.isBlank(newAdminBO.getImg64())) {
if (StringUtils.isBlank(newAdminBO.getPassword()) ||
StringUtils.isBlank(newAdminBO.getConfirmPassword())
) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_PASSWORD_NULL_ERROR);
}
}
// 2. 密码不为空,则必须判断两次输入一致
if (StringUtils.isNotBlank(newAdminBO.getPassword())) {
if (!newAdminBO.getPassword()
.equalsIgnoreCase(newAdminBO.getConfirmPassword())) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_PASSWORD_ERROR);
}
}
// 3. 校验用户名唯一
checkAdminExist(newAdminBO.getUsername());
// 4. 调用service存入admin信息
adminUserService.createAdminUser(newAdminBO);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult getAdminList(Integer page, Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult result = adminUserService.queryAdminList(page, pageSize);
return GraceJSONResult.ok(result);
}
@Override
public GraceJSONResult adminLogout(String adminId,
HttpServletRequest request,
HttpServletResponse response) {
// 从redis中删除admin的会话token
redis.del(REDIS_ADMIN_TOKEN + ":" + adminId);
// 从cookie中清理adming登录的相关信息
deleteCookie(request, response, "atoken");
deleteCookie(request, response, "aid");
deleteCookie(request, response, "aname");
return GraceJSONResult.ok();
}
}
service-api com/imooc/api/controller/admin/AdminMngControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "管理员admin维护", tags = {"管理员admin维护的controller"})
@RequestMapping("adminMng")
public interface AdminMngControllerApi {
@ApiOperation(value = "hello方法的接口", notes = "hello方法的接口", httpMethod = "POST")
@PostMapping("/adminLogin")
public GraceJSONResult adminLogin(@RequestBody AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response);
@ApiOperation(value = "查询admin用户名是否存在", notes = "查询admin用户名是否存在", httpMethod = "POST")
@PostMapping("/adminIsExist")
public GraceJSONResult adminIsExist(@RequestParam String username);
@ApiOperation(value = "创建admin", notes = "创建admin", httpMethod = "POST")
@PostMapping("/addNewAdmin")
public GraceJSONResult addNewAdmin(@RequestBody NewAdminBO newAdminBO,
HttpServletRequest request,
HttpServletResponse response);
@ApiOperation(value = "查询admin列表", notes = "查询admin列表", httpMethod = "POST")
@PostMapping("/getAdminList")
public GraceJSONResult getAdminList(
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页查询每一页显示的条数", required = false)
@RequestParam Integer pageSize);
@ApiOperation(value = "admin退出登录", notes = "admin退出登录", httpMethod = "POST")
@PostMapping("/adminLogout")
public GraceJSONResult adminLogout(@RequestParam String adminId,
HttpServletRequest request,
HttpServletResponse response);
HttpServletResponse response);
}
service-api com/imooc/api/BaseController.java
public void setCookieValue(HttpServletRequest request,
HttpServletResponse response,
String cookieName,
String cookieValue,
Integer maxAge) {
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(maxAge);
// cookie.setDomain("imoocnews.com");
cookie.setDomain(DOMAIN_NAME);
cookie.setPath("/");//都用cookie
response.addCookie(cookie);//把cookie传入
}
public void deleteCookie(HttpServletRequest request,HttpServletResponse response,String cookieName){
try {
String deleteValue = URLEncoder.encode("", "utf-8");
setCookieValue(request, response, cookieName, deleteValue, COOKIE_DELETE);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
人脸业务流程图梳理
Chrome开启视频调试模式
在谷歌浏览器中打开【每一次重启电脑都要操作】
chrome://flags/#unsafely-treat-insecure-origin-as-secure
|————————————————————————————————|
| http://admin.imoocnews.com:9090,http://admin.imoocnews.com |
|—— ——————————————————————————————|
http://admin.imoocnews.com:9090/imooc-news/admin/adminMng.html
可以获取人脸
MongoDB概念 [人脸数据存储]可以存储JSON数据
- NoSql 数据库
- 内存级别查询
- 不支持事务
- 非并发读写 请求并发数据量大
- GridFS 小文件存储
MongoDB术语
数据库 | ElasticSearch | MongoDB |
---|---|---|
database | es库 | database |
table表 | index索引 | collection数据集合 |
row行 (记录) | document文档 (json) | document文档 (json) |
column 字段列 | field域 | field域 |
index索引 | - | index索引 |
join表关联查询 | - | - |
pk主键 | _id | _id |
MongoDB数据结构
UserList:[
{
userId: "1001",
username: "lee",
age: 18
},
{
userId: "1002",
username: "jay",
age: 20,
sex: "boy"
}
]
----------------------------------------------------------
UserList --> collection
{} --> document
属性 --> column
MogoDB安装与配置使用
https://www.mongodb.com/try/download/enterprise
将mongodb-linux-x86_64-rhel70-4.2.8传入虚拟机
[imooc@imooc ~]$ tar -zxvf mongodb-linux-x86_64-rhel70-4.2.8.tgz
[imooc@imooc ~]$ sudo mv mongodb-linux-x86_64-rhel70-4.2.8 /usr/local/mongodb
[imooc@imooc ~]$ cd /usr/local/
[imooc@imooc local]$ ll
drwxrwxr-x. 3 imooc imooc 135 7月 16 19:46 mongodb
[imooc@imooc local]$ cd mongodb/
[imooc@imooc mongodb]$ ll
总用量 312
drwxrwxr-x. 2 imooc imooc 231 7月 16 19:46 bin
-rw-r--r--. 1 imooc imooc 30608 6月 12 2020 LICENSE-Community.txt
-rw-r--r--. 1 imooc imooc 16726 6月 12 2020 MPL-2
-rw-r--r--. 1 imooc imooc 2617 6月 12 2020 README
-rw-r--r--. 1 imooc imooc 75405 6月 12 2020 THIRD-PARTY-NOTICES
-rw-r--r--. 1 imooc imooc 183512 6月 12 2020 THIRD-PARTY-NOTICES.gotools
[imooc@imooc mongodb]$ cd bin/
[imooc@imooc bin]$ pwd
/usr/local/mongodb/bin
[imooc@imooc bin]$ sudo vim /etc/profile
最下面添加:
export JAVA_HOME=/usr/java/jdk1.8.0_222-ea
export CLASSPATH=.:%JAVA_HOME%/lib/dt.jar:%JAVA_HOME%/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin
#set mogodb config
export PATH=/usr/local/mongodb/bin:$PATH
#修改 /etc/profile 文件后,需要重新加载这个文件才能使新配置生效。你可以执行以下命令:
[imooc@imooc bin]$ source /etc/profile
[imooc@imooc bin]$ mongo --version
MongoDB shell version v4.2.8
git version: 43d25964249164d76d5e04dd6cf38f6111e21f5f
OpenSSL version: OpenSSL 1.0.1e-fips 11 Feb 2013
allocator: tcmalloc
modules: none
build environment:
distmod: rhel70
distarch: x86_64
target_arch: x86_64
[imooc@imooc bin]$ cd /usr/local/mongodb/
[imooc@imooc mongodb]$ pwd
/usr/local/mongodb
#创建数据存储目录
[imooc@imooc mongodb]$ mkdir data/db -p #出来一个data
[imooc@imooc mongodb]$ ll
总用量 312
drwxrwxr-x. 2 imooc imooc 231 7月 16 19:46 bin
drwxrwxr-x. 3 imooc imooc 16 7月 16 20:00 data
[imooc@imooc mongodb]$ cd data
[imooc@imooc data]$ ll
总用量 0
drwxrwxr-x. 2 imooc imooc 6 7月 16 20:00 db
[imooc@imooc data]$ mkdir logs
[imooc@imooc data]$ ll
总用量 0
drwxrwxr-x. 2 imooc imooc 6 7月 16 20:00 db
drwxrwxr-x. 2 imooc imooc 6 7月 16 20:00 logs
[imooc@imooc data]$ cd logs/
[imooc@imooc logs]$ pwd
/usr/local/mongodb/data/logs
[imooc@imooc logs]$ touch mongodb.log
[imooc@imooc logs]$ ll
总用量 0
-rw-rw-r--. 1 imooc imooc 0 7月 16 20:01 mongodb.log
[imooc@imooc logs]$ cd ..
[imooc@imooc logs]$ cd ..
[imooc@imooc mongodb]$ vim mongodb.conf
port=27017
# datasource path
dbpath=/user/local/mongodb/data/db
# log path
logpath=/usr/local/mongodb/data/logs/mongodb.log
# append log
logappend=true
# cut useless log
quiet=true
# back desktop auto run
fork=true
# Maxcontect
maxConns=100
# Not open Verify permissions
noauth=true
# open Verify permissions
# auth=true
# open log => true
journal=true
# clash
bind_ip=0.0.0.0
[imooc@imooc mongodb]$ sudo yum install net-snmp
#错误:软件包:1:net-snmp-agent-libs-5.7.2-49.el7_9.4.x86_64 (updates)
需要:libmysqlclient.so.18(libmysqlclient_18)(64bit)
#错误:软件包:1:net-snmp-5.7.2-49.el7_9.4.x86_64 (updates)
需要:libmysqlclient.so.18()(64bit)
#错误:软件包:1:net-snmp-agent-libs-5.7.2-49.el7_9.4.x86_64 (updates)
需要:libmysqlclient.so.18()(64bit)
# cd /usr/local/mongodb/
[imooc@imooc mongodb]$ mongod -f mongodb.conf
about to fork child process, waiting until server is ready for connections.
forked process: 4989
child process started successfully, parent exiting
[imooc@imooc mongodb]$ ps aux | grep mongod
imooc 4989 1.8 4.2 1550916 78280 ? Sl 20:39 0:00 mongod -f mongodb.conf
imooc 5105 0.0 0.0 112824 988 pts/0 S+ 20:40 0:00 grep --color=auto mongod
[imooc@imooc mongodb]$ ps -ef|grep mongodb
imooc 4989 1 0 20:39 ? 00:00:02 mongod -f mongodb.conf
imooc 5201 2948 0 20:44 pts/0 00:00:00 grep --color=auto mongodb
尝试连接到 MongoDB 实例:
[imooc@imooc mongodb]$ mongo --port 27017
可视化管理工具【MongoDB】
在Navicat里新建链接MongoDB
主机:192.168.170.135
右键新建数据库school → 集合 → 右键新建 左上角保存student
[imooc@imooc mongodb]$ vim mongodb.conf
##### 启用用户账号权限
# Not open Verify permissions
# noauth=true
# open Verify permissions
auth=true
#重启服务
[imooc@imooc mongodb]$ ps -ef|grep mongodb
imooc 4989 1 0 20:39 ? 00:00:05 mongod -f mongodb.conf
imooc 5380 2948 0 20:54 pts/0 00:00:00 grep --color=auto mongodb
[imooc@imooc mongodb]$ kill -2 4989
[imooc@imooc mongodb]$ ps -ef|grep mongodb
imooc 5395 2948 0 20:54 pts/0 00:00:00 grep --color=auto mongodb
[imooc@imooc mongodb]$ mongod -f mongodb.conf
about to fork child process, waiting until server is ready for connections.
forked process: 5419
child process started successfully, parent exiting
[imooc@imooc mongodb]$ mongo
MongoDB shell version v4.2.8
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("c87ffbd9-69cd-4e29-badd-5b86a314f428") }
MongoDB server version: 4.2.8
> use admin
switched to db admin
> db.createUser({user:"root",pwd:"root",roles:["root"]})
Successfully added user: { "user" : "root", "roles" : [ "root" ] }
> db.auth("root","root")
1
> show users
{
"_id" : "admin.root",
"userId" : UUID("2ced1f0a-8de4-4fab-9cb8-8e420fe9dcba"),
"user" : "root",
"db" : "admin",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
],
"mechanisms" : [
"SCRAM-SHA-1",
"SCRAM-SHA-256"
]
}
>
#后面关闭连接 编辑数据库 新增密码登录 root root
整合SpringBoot 【GridFS】
<!-- 引入 mongodb 依赖 -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
</dependency>
<dependency>
service-api com/imooc/api/controller/files/FileUploadControllerApi.java
package com.imooc.api.controller.files;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploadControllerApi {
@ApiOperation(value = "上传用户头像",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
//不可以通过swagger2调用的
/**
* 文件上传到mongodb的gridfs中
* @param newAdminBO
* @return
* @throws Exception
*/
@PostMapping("/uploadToGridFS")
public GraceJSONResult uploadToGridFS(@RequestBody NewAdminBO newAdminBO) throws Exception;
}
service-files application.yml
data:
mongodb:
uri: mongodb://root:root@192.168.170.135:27017
database: imooc-news
实现人脸入库 【GridFS】
service-files com/imooc/files/controller/FileUploadController.java
...
@Autowired
private GridFSBucket gridFSBucket;
...
@Override
public GraceJSONResult uploadToGridFS(NewAdminBO newAdminBO) throws Exception {
// 获得图片的base64字符串
String file64 = newAdminBO.getImg64();
// 将base64字符串转换为byte数组
byte[] bytes = new BASE64Decoder().decodeBuffer(file64.trim());
// 转换为输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
//上传到gridfs中
ObjectId fileId = gridFSBucket.uploadFromStream(newAdminBO.getUsername() + ".png", inputStream);
// 获取文件在gridfs中的主键id
String fileIdStr = fileId.toString();
// 下次提交的时候会提交到后端
return GraceJSONResult.ok(fileIdStr);
}
http://admin.imoocnews.com:9090/imooc-news/admin/adminMng.html
注册并且提交人脸信息
去Navicat → MongoDB → imooc-news → GridFS存储桶 → fs → admin456.png
service-files com/imooc/files/controller/FileUploadController.java
...
@Override
public GraceJSONResult uploadToGridFS(NewAdminBO newAdminBO) throws Exception {
// 获得图片的base64字符串
String file64 = newAdminBO.getImg64();
// 将base64字符串转换为byte数组
byte[] bytes = new BASE64Decoder().decodeBuffer(file64.trim());
// 转换为输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
//上传到gridfs中
ObjectId fileId = gridFSBucket.uploadFromStream(newAdminBO.getUsername() + ".png", inputStream);
// 获取文件在gridfs中的主键id
String fileIdStr = fileId.toString();
// 下次提交的时候会提交到后端
return GraceJSONResult.ok(fileIdStr);
}
service-api com/imooc/api/controller/files/FileUploadControllerApi.java
package com.imooc.api.controller.files;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploadControllerApi {
@ApiOperation(value = "上传用户头像",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
//不可以通过swagger2调用的
/**
* 文件上传到mongodb的gridfs中
* @param newAdminBO
* @return
* @throws Exception
*/
@PostMapping("/uploadToGridFS")
public GraceJSONResult uploadToGridFS(@RequestBody NewAdminBO newAdminBO) throws Exception;
}
service-files com/imooc/files/GridFSConfig.java
package com.imooc.files;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component //可以被容器访问到
public class GridFSConfig {
@Value("${spring.data.mongodb.database}")
private String mongodb;
@Bean
public GridFSBucket gridFSBucket(MongoClient mongoClient){
MongoDatabase mongoDatabase = mongoClient.getDatabase(mongodb);
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);//存入mongodatabase
return bucket;
}
}
查看admin人脸信息 【GridFS】
service-files com/imooc/files/controller/FileUploaderController.java
@Override
public GraceJSONResult uploadToGridFS(NewAdminBO newAdminBO)
throws Exception {
// 获得图片的base64字符串
String file64 = newAdminBO.getImg64();
// 将base64字符串转换为byte数组
byte[] bytes = new BASE64Decoder().decodeBuffer(file64.trim());
// 转换为输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
// 上传到gridfs中
ObjectId fileId = gridFSBucket.uploadFromStream(newAdminBO.getUsername() + ".png", inputStream);
// 获得文件在gridfs中的主键id
String fileIdStr = fileId.toString();
return GraceJSONResult.ok(fileIdStr);
}
@Override
public void readInGridFS(String faceId,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 0. 判断参数
if (StringUtils.isBlank(faceId) || faceId.equalsIgnoreCase("null")) {
GraceException.display(ResponseStatusEnum.FILE_NOT_EXIST_ERROR);
}
// 1. 从gridfs中读取
File adminFace = readGridFSByFaceId(faceId);
// 2. 把人脸图片输出到浏览器
FileUtils.downloadFileByStream(response, adminFace);
}
private File readGridFSByFaceId(String faceId) throws Exception {
GridFSFindIterable gridFSFiles
= gridFSBucket.find(Filters.eq("_id", new ObjectId(faceId)));
GridFSFile gridFS = gridFSFiles.first();
if (gridFS == null) {
GraceException.display(ResponseStatusEnum.FILE_NOT_EXIST_ERROR);
}
String fileName = gridFS.getFilename();
System.out.println(fileName);
// 获取文件流,保存文件到本地或者服务器的临时目录
File fileTemp = new File("/workspace/temp_face");
if (!fileTemp.exists()) {
fileTemp.mkdirs();
}
File myFile = new File("/workspace/temp_face/" + fileName);
// 创建文件输出流
OutputStream os = new FileOutputStream(myFile);
// 下载到服务器或者本地
gridFSBucket.downloadToStream(new ObjectId(faceId), os);
return myFile;
}
service-api com/imooc/api/controller/files/FileUploaderControllerApi.java
package com.imooc.api.controller.files;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploaderControllerApi {
@ApiOperation(value = "上传用户头像",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
//不可以通过swagger2调用的
/**
* 文件上传到mongodb的gridfs中
* @param newAdminBO
* @return
* @throws Exception
*/
@PostMapping("/uploadToGridFS")
public GraceJSONResult uploadToGridFS(@RequestBody NewAdminBO newAdminBO) throws Exception;
@GetMapping("/readInGridFS")
public void readInGridFS(String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
//AdminCookieToken也可以获得faceId
阿里AI人脸识别介绍
获得人脸faceId【人脸登录】
service-api com/imooc/api/controller/admin/AdminMngControllerApi.java
@ApiOperation(value = "admin管理员的人脸登录", notes = "admin管理员的人脸登录", httpMethod = "POST")
@PostMapping("/adminFaceLogin")
public GraceJSONResult adminFaceLogin(@RequestBody AdminLoginBO adminLoginBO,
HttpServletRequest request,
HttpServletResponse response);
service-admin com/imooc/admin/controller/AdminMngController.java
@Override
public GraceJSONResult adminFaceLogin(AdminLoginBO adminLoginBO, HttpServletRequest request, HttpServletResponse response) {
// 0. 判断用户名和人脸信息不能为空
if(StringUtils.isBlank(adminLoginBO.getUsername())){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_USERNAME_NULL_ERROR);
}
String tempFace64 = adminLoginBO.getImg64();
if (StringUtils.isBlank(tempFace64)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 1. 从数据库中查询出faceId
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
String adminFaceId = admin.getFaceId();
if (StringUtils.isBlank(adminFaceId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 2. 请求文件服务,获得人懒数据的base64数据
// 3. 调用阿里ai进行人脸对比识别,判断可信度,从而实现人脸登录
// 4. admin登录后的数据设置,redis与cookie
return null;
}
service-api com/imooc/api/controller/files/FileUploaderControllerApi.java
package com.imooc.api.controller.files;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewAdminBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploaderControllerApi {
@ApiOperation(value = "上传用户头像",notes = "hello方法的接口",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
//不可以通过swagger2调用的
/**
* 文件上传到mongodb的gridfs中
* @param newAdminBO
* @return
* @throws Exception
*/
@PostMapping("/uploadToGridFS")
public GraceJSONResult uploadToGridFS(@RequestBody NewAdminBO newAdminBO) throws Exception;
@GetMapping("/readInGridFS")
public void readInGridFS(String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception;
/**
* 从gridfs中读取图片内容 返回base64数据
* @param faceId
* @param request
* @param response
* @return
* @throws Exception
*/
@GetMapping("/readFace64InGridFS")
public GraceJSONResult readFace64InGridFS(String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
service-files com/imooc/files/controller/FileUploaderController.java
@Override
public GraceJSONResult readFace64InGridFS(String faceId, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 0. 获得gridfs中人脸文件
File myface = readGridFSByFaceId(faceId);
// 1. 转换人脸为base64
String base64Face = FileUtils.fileToBase64(myface);
return GraceJSONResult.ok(base64Face);
}
整合RestTemplate服务通信 【人脸登录】
service-admin com/imooc/admin/controller/AdminMngController.java
@RestController
public class AdminMngController extends BaseController implements AdminMngControllerApi {
@Autowired
private RestTemplate restTemplate;
@Override
public GraceJSONResult adminFaceLogin(AdminLoginBO adminLoginBO, HttpServletRequest request, HttpServletResponse response) {
// 0. 判断用户名和人脸信息不能为空
if(StringUtils.isBlank(adminLoginBO.getUsername())){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_USERNAME_NULL_ERROR);
}
String tempFace64 = adminLoginBO.getImg64();
if (StringUtils.isBlank(tempFace64)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 1. 从数据库中查询出faceId
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
String adminFaceId = admin.getFaceId();
if (StringUtils.isBlank(adminFaceId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 2. 请求文件服务,获得人懒数据的base64数据
String fileServerUrlExecute = "http://files.imoocnews.com:8004/fs/readFace64InGridFS?faceId=" + adminFaceId;
ResponseEntity<GraceJSONResult> responseEntity = restTemplate.getForEntity(fileServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
String base64DB = (String)bodyResult.getData();
// 3. 调用阿里ai进行人脸对比识别,判断可信度,从而实现人脸登录
// 4. admin登录后的数据设置,redis与cookie
return null;
}
}
service-api com/imooc/api/config/CloudConfig.java
package com.imooc.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CloudConfig {
public CloudConfig() {
}
/**
* 会基于OKHttp3的配置来实例RestTemplate
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
实现人脸对比进行登录【人脸对比】没买人脸识别服务,简单写了一下
service-admin com/imooc/admin/controller/AdminMngController.java
@Override
public GraceJSONResult adminFaceLogin(AdminLoginBO adminLoginBO, HttpServletRequest request, HttpServletResponse response) {
// 0. 判断用户名和人脸信息不能为空
if(StringUtils.isBlank(adminLoginBO.getUsername())){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_USERNAME_NULL_ERROR);
}
String tempFace64 = adminLoginBO.getImg64();
if (StringUtils.isBlank(tempFace64)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 1. 从数据库中查询出faceId
AdminUser admin = adminUserService.queryAdminByUsername(adminLoginBO.getUsername());
String adminFaceId = admin.getFaceId();
if (StringUtils.isBlank(adminFaceId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_NULL_ERROR);
}
// 2. 请求文件服务,获得人懒数据的base64数据
String fileServerUrlExecute = "http://files.imoocnews.com:8004/fs/readFace64InGridFS?faceId=" + adminFaceId;
ResponseEntity<GraceJSONResult> responseEntity = restTemplate.getForEntity(fileServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
String base64DB = (String)bodyResult.getData();
// 3. 调用阿里ai进行人脸对比识别,判断可信度,从而实现人脸登录
boolean result = faceVerifyUtils.faceVerify(FaceVerifyType.BASE64.type,
tempFace64,
base64DB,
60);
if (!result){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ADMIN_FACE_LOGIN_ERROR);
}
// 4. admin登录后的数据设置,redis与cookie
doLoginSettings(admin,request,response);
return GraceJSONResult.ok();
}
dev-common com/imooc/utils/FaceVerifyUtils.java
package com.imooc.utils;
import com.aliyuncs.utils.Base64Helper;
import com.imooc.enums.FaceVerifyType;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.extend.AliyunResource;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.SimpleTimeZone;
@Component
public class FaceVerifyUtils {
final static Logger logger = LoggerFactory.getLogger(FaceVerifyUtils.class);
@Autowired
private AliyunResource aliyunResource;
//网关地址
private static final String gateway = "https://dtplus-cn-shanghai.data.aliyuncs.com/face/verify";
/*
* 计算MD5+BASE64
*/
public static String MD5Base64(String s) {
if (s == null)
return null;
String encodeStr = "";
byte[] utfBytes = s.getBytes();
MessageDigest mdTemp;
try {
mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(utfBytes);
byte[] md5Bytes = mdTemp.digest();
Base64Helper b64Encoder = new Base64Helper();
encodeStr = b64Encoder.encode(md5Bytes);
} catch (Exception e) {
throw new Error("Failed to generate MD5 : " + e.getMessage());
}
return encodeStr;
}
/*
* 计算 HMAC-SHA1
*/
public static String HMACSha1(String data, String key) {
String result;
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
result = (new Base64Helper()).encode(rawHmac);
} catch (Exception e) {
throw new Error("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
/*
* 等同于javaScript中的 new Date().toUTCString();
*/
public static String toGMTString(Date date) {
SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK);
df.setTimeZone(new SimpleTimeZone(0, "GMT"));
return df.format(date);
}
/**
* 发送POST请求 进行两张图的人脸对比
* @param type
* 0: 通过url识别,参数image_url不为空;1: 通过图片content识别,参数content不为空
* @param face1
* type为0,则传入图片url,为1则传入base64
* @param face2
* type为0,则传入图片url,为1则传入base64
* @return
*/
//如果发送的是转换为base64编码后后面加请求参数type为1,如果请求的是图片的url则不用加type参数。
public String sendPostVerifyFace(int type, String face1, String face2) throws Exception {
String body = "";
if (type == FaceVerifyType.BASE64.type) {
body = "{\"content_1\": \"" + face1 + "\", \"content_2\":\"" + face2 + "\", \"type\":\"" + type + "\"}";
} else if (type == FaceVerifyType.IMAGE_URL.type) {
body = "{\"image_url_1\": \"" + face1 + "\", \"image_url_2\":\"" + face2 + "\", \"type\":\"" + type + "\"}";
} else {
GraceException.display(ResponseStatusEnum.FACE_VERIFY_TYPE_ERROR);
}
// String body = "{\"content_1\": \"" + face1 + "\", \"content_2\":\"" + face2 + "\", \"type\":\"" + "1" + "\"}";
PrintWriter out = null;
BufferedReader in = null;
String result = "";
int statusCode = 200;
try {
URL realUrl = new URL(gateway);
/*
* http header 参数
*/
String method = "POST";
// 返回值类型
String accept = "application/json";
// 请求内容类型
String content_type = "application/json";
String path = realUrl.getFile();
// GMT时间
String date = toGMTString(new Date());
// 1.对body做MD5+BASE64加密
String bodyMd5 = MD5Base64(body);
String stringToSign = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date + "\n"
+ path;
// 2.计算 HMAC-SHA1
String signature = HMACSha1(stringToSign, aliyunResource.getAccessKeySecret());
// 3.得到 authorization header
String authHeader = "Dataplus " + aliyunResource.getAccessKeyID() + ":" + signature;
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Accept", accept);
conn.setRequestProperty("Content-type", content_type);
conn.setRequestProperty("Date", date);
// 认证信息
conn.setRequestProperty("Authorization", authHeader);
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(body);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
statusCode = ((HttpURLConnection) conn).getResponseCode();
if (statusCode != 200) {
in = new BufferedReader(new InputStreamReader(((HttpURLConnection) conn).getErrorStream()));
} else {
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
}
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (statusCode != 200) {
throw new IOException("\nHttp StatusCode: " + statusCode + "\nErrorMessage: " + result);
}
return result;
}
/**
*
* @param type
* @param face1
* @param face2
* @param targetConfidence
* 目标可信度,自定义阈值
* @return
*/
public boolean faceVerify(int type, String face1, String face2, double targetConfidence) {
String response = null;
try {
response = sendPostVerifyFace(type, face1, face2);
} catch (Exception e) {
e.printStackTrace();
}
Map<String, String> map = JsonUtils.jsonToPojo(response, Map.class);
Object confidenceStr = map.get("confidence");
Double responseConfidence = (Double)confidenceStr;
logger.info("人脸对比结果:{}", responseConfidence);
// System.out.println(response.toString());
// System.out.println(map.toString());
if (responseConfidence > targetConfidence) {
return true;
} else {
return false;
}
}
/**
*
* 将图片转换为Base64
* 将base64编码字符串解码成img图片
* @param imgUrl
* @return
*/
public String getImgBase64(String imgUrl){
ByteArrayOutputStream data = new ByteArrayOutputStream();
try {
// 创建URL
URL url = new URL(imgUrl);
byte[] by = new byte[1024];
// 创建链接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
InputStream is = conn.getInputStream();
// 将内容放到内存中
int len = -1;
while ((len = is.read(by)) != -1) {
data.write(by, 0, len);
}
is.close();
} catch (IOException e) {
e.printStackTrace();
}
// 对字节数组Base64编码
return Base64.encodeBase64String(data.toByteArray());
}
// public static void main(String[] args) {
// String face3 = "http://122.152.205.72:88/group1/M00/00/05/CpoxxF5MvvGAfnLXAAIHiv37wNk363.jpg";
// String face4 = "http://122.152.205.72:88/group1/M00/00/05/CpoxxF5Mv3yAH74mAACOiTd9pO4462.jpg";
//
// boolean result = new FaceVerifyUtils().faceVerify(FaceVerifyType.IMAGE_URL.type, face3, face4, 60);
//
// logger.info("人脸对比是否成功:{}", result);
// }
}
MongoDB使用场景 【分担数据库的大数据量】
- GridFS小文件存储
- 历史数据快照 [买的东西涨价后 还是原来的价格] 【数据量大存入MongoDB】
- 用户浏览记录
- 客服聊天记录 [不是核心数据 可以剥离]
这些不建议放在Redis里 因为Redis是存储在内存里的 [内存很贵 成本很大]
友情连接保存与更新 【MongoDB】
对连接的一些逻辑校验
service-admin application.yml 【加上mongodb配置】
data:
mongodb:
uri: mongodb://root:root@192.168.170.135:27017
database: imooc-news
service-admin Application 【注释exclude 把mongodb配置进来】
package com.imooc.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication //(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.admin.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
dev-model com/imooc/pojo/bo/SaveFriendLinkBO.java
package com.imooc.pojo.bo;
import com.imooc.validate.CheckUrl;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class SaveFriendLinkBO {
private String id;
@NotBlank(message = “友情链接名不能为空”)
private String linkName;
@NotBlank(message = “友情链接地址不能为空”)
@CheckUrl 【ctrl+左键 显示↓ CheckUrl接口】
@CheckName 【 //不能有空格 不能为空 字符串长度要在6-12位】
private String linkUrl;
@NotNull(message = “请选择保留或删除”)
private Integer isDelete;
}Getter+Setter
dev-model com/imooc/validate/CheckUrl.java
package com.imooc.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckUrlValidate.class)
public @interface CheckUrl {
String message() default "Url不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
dev-model com/imooc/validate/CheckName.java
package com.imooc.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckUrlValidate.class)
public @interface CheckName {
String message() default "Name不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
dev-model com/imooc/validate/CheckUrlValidate.java
package com.imooc.validate;
import com.imooc.utils.UrlUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CheckUrlValidate implements ConstraintValidator<CheckUrl, String> {
@Override
public boolean isValid(String url, ConstraintValidatorContext context) {
return UrlUtil.verifyUrl(url.trim());
}
}
dev-model com/imooc/validate/CheckNameValidate.java
package com.imooc.validate;
import com.imooc.utils.UrlUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CheckNameValidate implements ConstraintValidator<CheckName, String> {
@Override
public boolean isValid(String name, ConstraintValidatorContext context) {
return UrlUtil.verifyName(name.trim());
}
}
dev-common com/imooc/utils/UrlUtil.java 【Url+Name校验标准】
package com.imooc.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UrlUtil {
/**
* 验证是否是URL
* @param url
* @return
*/
public static boolean verifyUrl(String url){
// URL验证规则
// String regEx ="[A-Za-z]+://[A-Za-z0-9-_]+\\\\.[A-Za-z0-9-_%&\\?\\/.=]+";
String regEx = "^([hH][tT]{2}[pP]:/*|[hH][tT]{2}[pP][sS]:/*|[fF][tT][pP]:/*)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\/])+(\\?{0,1}(([A-Za-z0-9-~]+\\={0,1})([A-Za-z0-9-~]*)\\&{0,1})*)$";
// 编译正则表达式
Pattern pattern = Pattern.compile(regEx);
// 忽略大小写的写法
// Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(url);
// 字符串是否与正则表达式相匹配
boolean rs = matcher.matches();
return rs;
}
//不能有空格 不能为空 字符串长度要在6-12位
public static boolean verifyName(String name){
// Name验证规则
String nameEx = "^[^\\s]{6,12}$";
// 编译正则表达式
Pattern pattern = Pattern.compile(nameEx);
// 忽略大小写的写法
// Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(name);
// 字符串是否与正则表达式相匹配
boolean rs = matcher.matches();
return rs;
}
public static void main(String[] args) {
boolean res = verifyUrl("http://admin.imoocnews.com:9090/imooc-news/admin/friendLinks.html");
boolean nres = verifyName("Jerry");
System.out.println(nres);
}
}
dev-model pom.xml
<!-- 引入 mongodb 依赖 --> 【springboot整合mongodb】
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
真正的友链保存接口
service-api com/imooc/api/controller/admin/FriendLinkControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.BindingResult;
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.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "首页友情连接维护", tags = {"首页友情连接维护"})
@RequestMapping("friendLinkMng")
public interface FriendLinkControllerApi {
@ApiOperation(value = "新增或者修改友情连接", notes = "新增或者修改友情连接", httpMethod = "POST")
@PostMapping("/saveOrUpdateFriendLink")
public GraceJSONResult saveOrUpdateFriendLink(@RequestBody SaveFriendLinkBO saveFriendLinkBO,
BindingResult result);
}
service-admin com/imooc/admin/controller/FriendLinkController.java
package com.imooc.admin.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.FriendLinkControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import com.imooc.pojo.mo.FriendLinkMO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.Map;
@RestController
public class FriendLinkController extends BaseController implements FriendLinkControllerApi {
final static Logger logger = LoggerFactory.getLogger(FriendLinkController.class);
@Override
public GraceJSONResult saveOrUpdateFriendLink(SaveFriendLinkBO saveFriendLinkBO, BindingResult result) {
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
// saveFriendLinkBO -> ***Mo MongoDB校验的对象
FriendLinkMO friendLinkMO = new FriendLinkMO();
BeanUtils.copyProperties(saveFriendLinkBO,friendLinkMO);
friendLinkMO.setCreateTime(new Date());
friendLinkMO.setUpdateTime(new Date());
return GraceJSONResult.ok();
}
}
dev-model com/imooc/pojo/mo/FriendLinkMO.java
//这些都是设置到MongoDB数据库的名字
//@Document("FriendLink") //MongoDB文件起别名
public class FriendLinkMO {
@Id //作为MongDB的主键了
private String id;
@Field("link_name")
private String linkName;
@Field("link_url")
private String linkUrl;
@Field("is_delete")
private Integer isDelete;
@Field("create_time")
private Date createTime;
@Field("update_time")
private Date updateTime;
}Getter + Setter
Repository持久层操作保存记录
service-api com/imooc/api/controller/admin/FriendLinkControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.BindingResult;
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.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "首页友情连接维护", tags = {"首页友情连接维护"})
@RequestMapping("friendLinkMng")
public interface FriendLinkControllerApi {
@ApiOperation(value = "新增或者修改友情连接", notes = "新增或者修改友情连接", httpMethod = "POST")
@PostMapping("/saveOrUpdateFriendLink")
public GraceJSONResult saveOrUpdateFriendLink(@RequestBody SaveFriendLinkBO saveFriendLinkBO,
BindingResult result);
}
service-admin com/imooc/admin/controller/FriendLinkController.java
package com.imooc.admin.controller;
import com.imooc.admin.repository.FriendLinkRepository;
import com.imooc.admin.service.FriendLinkService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.FriendLinkControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import com.imooc.pojo.mo.FriendLinkMO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.Map;
@RestController
public class FriendLinkController extends BaseController implements FriendLinkControllerApi {
final static Logger logger = LoggerFactory.getLogger(FriendLinkController.class);
@Autowired
private FriendLinkService friendLinkService;
@Override
public GraceJSONResult saveOrUpdateFriendLink(SaveFriendLinkBO saveFriendLinkBO, BindingResult result) {
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
// saveFriendLinkBO -> ***Mo MongoDB校验的对象
FriendLinkMO friendLinkMO = new FriendLinkMO();
BeanUtils.copyProperties(saveFriendLinkBO,friendLinkMO);
friendLinkMO.setCreateTime(new Date());
friendLinkMO.setUpdateTime(new Date());
friendLinkService.saveOrUpdateFriendLink(friendLinkMO);
return GraceJSONResult.ok();
}
}
// http://admin.imoocnews.com:9090/imooc-news/admin/friendLinks.html
/* 友情连接 →
链接名称:慕课网
链接地址:www.imooc.com
[新增/添加]
打开检查→Console
{"status":200,"msg":"操作成功!","success":true,"data":null}
打开数据库查看MongoDB→friendLinkMO有存入的数据即操作成功
*/
service-admin com/imooc/admin/service/FriendLinkService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.mo.FriendLinkMO;
import com.imooc.utils.PagedGridResult;
public interface FriendLinkService {
/**
* 新增或者更新友情链接
*/
public void saveOrUpdateFriendLink(FriendLinkMO friendLinkMO);
}
service-admin com/imooc/admin/service/impl/FriendLinkServiceImpl.java
package com.imooc.admin.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.imooc.admin.mapper.AdminUserMapper;
import com.imooc.admin.repository.FriendLinkRepository;
import com.imooc.admin.service.AdminUserService;
import com.imooc.admin.service.FriendLinkService;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.mo.FriendLinkMO;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
public class FriendLinkServiceImpl implements FriendLinkService {
@Autowired
private FriendLinkRepository friendLinkRepository;
@Override
public void saveOrUpdateFriendLink(FriendLinkMO friendLinkMO) {
friendLinkRepository.save(friendLinkMO); //有id更新 无id直接保存
}
}
service-admin com/imooc/admin/repository/FriendLinkRepository.java
package com.imooc.admin.repository;
import com.imooc.pojo.mo.FriendLinkMO;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface FriendLinkRepository extends MongoRepository<FriendLinkMO, String> { //持久层
// 内置提供了很多方法 find.. delete...
}
友情链接查询列表 【MongoDB】
@Document(“FriendLink”) //文件起别名 记得要在MongoDB里面找这个 下面搜索的都在这个文件里面
service-api com/imooc/api/controller/admin/FriendLinkControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.AdminLoginBO;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.BindingResult;
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.RequestParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Api(value = "首页友情连接维护", tags = {"首页友情连接维护"})
@RequestMapping("friendLinkMng")
public interface FriendLinkControllerApi {
@ApiOperation(value = "新增或者修改友情连接", notes = "新增或者修改友情连接", httpMethod = "POST")
@PostMapping("/saveOrUpdateFriendLink")
public GraceJSONResult saveOrUpdateFriendLink(@RequestBody SaveFriendLinkBO saveFriendLinkBO,
BindingResult result);
@ApiOperation(value = "查询改友情连接列表", notes = "查询改友情连接列表", httpMethod = "POST")
@PostMapping("/getFriendLinkList")
public GraceJSONResult getFriendLinkList();
}
service-admin com/imooc/admin/controller/FriendLinkController.java
package com.imooc.admin.controller;
import com.imooc.admin.repository.FriendLinkRepository;
import com.imooc.admin.service.FriendLinkService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.FriendLinkControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.SaveFriendLinkBO;
import com.imooc.pojo.mo.FriendLinkMO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.Map;
@RestController
public class FriendLinkController extends BaseController implements FriendLinkControllerApi {
final static Logger logger = LoggerFactory.getLogger(FriendLinkController.class);
@Autowired
private FriendLinkService friendLinkService;
@Override
public GraceJSONResult saveOrUpdateFriendLink(SaveFriendLinkBO saveFriendLinkBO, BindingResult result) {
if (result.hasErrors()){
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
// saveFriendLinkBO -> ***Mo MongoDB校验的对象
FriendLinkMO friendLinkMO = new FriendLinkMO();
BeanUtils.copyProperties(saveFriendLinkBO,friendLinkMO);
friendLinkMO.setCreateTime(new Date());
friendLinkMO.setUpdateTime(new Date());
friendLinkService.saveOrUpdateFriendLink(friendLinkMO);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult getFriendLinkList() {
//【用了FriendLinkRepository里面的】extends MongoRepository 中的简单增删改查
// 里面的删除是逻辑删除
return GraceJSONResult.ok(friendLinkService.queryAllFriendLinkList());
}
}
// http://admin.imoocnews.com:9090/imooc-news/admin/friendLinks.html
service-admin com/imooc/admin/service/FriendLinkService.java
package com.imooc.admin.service;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.mo.FriendLinkMO;
import com.imooc.utils.PagedGridResult;
import java.util.List;
public interface FriendLinkService {
/**
* 新增或者更新友情链接
*/
public void saveOrUpdateFriendLink(FriendLinkMO friendLinkMO);
/**
* 查询友情链接
*/
public List<FriendLinkMO> queryAllFriendLinkList();
}
service-admin com/imooc/admin/service/impl/FriendLinkServiceImpl.java
package com.imooc.admin.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.imooc.admin.mapper.AdminUserMapper;
import com.imooc.admin.repository.FriendLinkRepository;
import com.imooc.admin.service.AdminUserService;
import com.imooc.admin.service.FriendLinkService;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AdminUser;
import com.imooc.pojo.bo.NewAdminBO;
import com.imooc.pojo.mo.FriendLinkMO;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
public class FriendLinkServiceImpl implements FriendLinkService {
@Autowired
private FriendLinkRepository friendLinkRepository;
@Override
public void saveOrUpdateFriendLink(FriendLinkMO friendLinkMO) {
friendLinkRepository.save(friendLinkMO); //有id更新 无id直接保存
}
@Override
public List<FriendLinkMO> queryAllFriendLinkList() {
// Pageable pageable = PageRequest.of(1,10);
// friendLinkRepository.findAll(pageable);
return friendLinkRepository.findAll();
}
}
友情链接删除 【MongoDB】[增加真实删除]
service-api com/imooc/api/controller/admin/FriendLinkControllerApi.java
...
/*
@Api(value = "首页友情连接维护", tags = {"首页友情连接维护"})
@RequestMapping("friendLinkMng")
public interface FriendLinkControllerApi {
@ApiOperation(value = "新增或者修改友情连接", notes = "新增或者修改友情连接", httpMethod = "POST")
@PostMapping("/saveOrUpdateFriendLink")
public GraceJSONResult saveOrUpdateFriendLink(@RequestBody SaveFriendLinkBO saveFriendLinkBO,
BindingResult result);
@ApiOperation(value = "查询改友情连接列表", notes = "查询改友情连接列表", httpMethod = "POST")
@PostMapping("/getFriendLinkList")
public GraceJSONResult getFriendLinkList();
*/
@ApiOperation(value = "删除改友情连接列表", notes = "删除改友情连接列表", httpMethod = "POST")
@PostMapping("/delete")
public GraceJSONResult delete(@RequestParam String linkId);
}
service-admin com/imooc/admin/controller/FriendLinkController.java
@Override
public GraceJSONResult delete(String linkId) {
friendLinkService.delete(linkId);
return GraceJSONResult.ok();
}
service-admin com/imooc/admin/service/FriendLinkService.java
/**
* 删除友情链接
*/
public void delete(String linkId);
service-admin com/imooc/admin/service/AdminUserService.java
@Override
public void delete(String linkId) {
friendLinkRepository.deleteById(linkId);
}
service-api com/imooc/api/config/InterceptorConfig.java 【增加友链拦截器】
/*
package com.imooc.api.config;
import com.imooc.api.interceptors.AdminTokenInterceptor;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UserActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UserActiveInterceptor userActiveInterceptor() {
return new UserActiveInterceptor();
}
@Bean
public AdminTokenInterceptor adminTokenInterceptor() {
return new AdminTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace");
registry.addInterceptor(adminTokenInterceptor())//继续添加拦截器:查询admin列表 创建新admin用户
.addPathPatterns("/adminMng/adminIsExist")
.addPathPatterns("/adminMng/addNewAdmin")
.addPathPatterns("/adminMng/getAdminList")
.addPathPatterns("/fs/uploadToGridFS")
*/
.addPathPatterns("/friendLinkMng/saveOrUpdateFriendLink")
.addPathPatterns("/friendLinkMng/getFriendLinkList")
.addPathPatterns("/friendLinkMng/delete");
}
}
【作业】文章分类管理 [新增或修改分类、查询分类列表、用户端查询分类列表]
http://admin.imoocnews.com:9090/imooc-news/admin/categoryMng.html
service-api com/imooc/api/controller/admin/CategoryMngControllerApi.java
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.SaveCatrgoryBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Api(value = "文章分类维护", tags = {"文章分类维护controller"})
@RequestMapping("categoryMng")
public interface CategoryMngControllerApi {
@PostMapping("saveOrUpdateCategory")
@ApiOperation(value = "新增或修改分类", notes = "新增或修改分类", httpMethod = "POST")
public GraceJSONResult saveOrUpdateCategory(@RequestBody @Valid SaveCatrgoryBO saveCatrgoryBO,
BindingResult result);
@PostMapping("getCatList")
@ApiOperation(value = "查询分类列表", notes = "查询分类列表", httpMethod = "POST")
public GraceJSONResult getCatList();
@GetMapping("getCats")
@ApiOperation(value = "用户端查询分类列表", notes = "用户端查询分类列表", httpMethod = "GET")
public GraceJSONResult getCats();
}
service-admin com/imooc/admin/controller/CategoryMngController.java
package com.imooc.admin.controller;
import com.imooc.admin.service.CategoryService;
import com.imooc.api.BaseController;
import com.imooc.api.controller.admin.CategoryMngControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.SaveCatrgoryBO;
import com.imooc.utils.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class CategoryMngController extends BaseController implements CategoryMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(CategoryMngController.class);
@Autowired
private CategoryService categoryService;
@Override
public GraceJSONResult saveOrUpdateCategory(SaveCatrgoryBO saveCatrgoryBO, BindingResult result) {
if (result.hasErrors()){
// 判断BindingResult是否保存错误的验证信息,如果有,则直接return
Map<String, String> errorMap = getErrors(result);
return GraceJSONResult.errorMap(errorMap);
}
Category newCat = new Category();
BeanUtils.copyProperties(saveCatrgoryBO,newCat);
// id为空新增,不为空修改
if (saveCatrgoryBO.getId() == null){
//查询新增的分类名称不能重复存在
boolean isExist = categoryService.queryCatIsExist(newCat.getName(), null);
if (!isExist){
//新增到数据库
categoryService.createCategory(newCat);
}else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.CATEGORY_EXIST_ERROR);
}
}else {
//查询修改的分类名称不能重复存在
boolean isExist = categoryService.queryCatIsExist(newCat.getName(), saveCatrgoryBO.getOldName());
if (!isExist){
//修改到数据库
categoryService.modifyCategory(newCat);
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.CATEGORY_EXIST_ERROR);
}
}
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult getCatList() {
List<Category> categoryList = categoryService.queryCategoryList();
return GraceJSONResult.ok(categoryList);
}
@Override
public GraceJSONResult getCats() {
// 先从redis中查询,如果有,则返回,如果没有,则查询数据库库后先放缓存,放返回
String allCatJson = redis.get(REDIS_ALL_CATEGORY);
List<Category> categoryList = null;
if (StringUtils.isBlank(allCatJson)) {
categoryList = categoryService.queryCategoryList();
redis.set(REDIS_ALL_CATEGORY, JsonUtils.objectToJson(categoryList));
} else {
categoryList = JsonUtils.jsonToList(allCatJson, Category.class);
}
return GraceJSONResult.ok(categoryList);
}
}
service-admin com/imooc/admin/service/CategoryService.java
package com.imooc.admin.service;
import com.imooc.pojo.Category;
import java.util.List;
public interface CategoryService {
/**
* 新增文章分类
*/
public void createCategory(Category category);
/**
* 修改文章分类列表
*/
public void modifyCategory(Category category);
/**
* 查询分类名是否已经存在
*/
public boolean queryCatIsExist(String catName, String oldCatName);
/**
* 获得文章分类列表
*/
public List<Category> queryCategoryList();
}
service-admin com/imooc/admin/service/impl/CategoryServiceImpl.java
package com.imooc.admin.service.impl;
import com.imooc.admin.mapper.CategoryMapper;
import com.imooc.admin.service.CategoryService;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.Category;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.List;
import static com.imooc.api.BaseController.REDIS_ALL_CATEGORY;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
public CategoryMapper categoryMapper;
@Autowired
public RedisOperator redis;
@Transactional
@Override
public void createCategory(Category category) {
// 分类不会很多,所以id不需要自增,这个表的数据也不会多到几万甚至分表,数据都会集中在一起
int result = categoryMapper.insert(category);
if (result != 1){
GraceException.display(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
/**
* 不建议如下做法:
* 1. 查询redis中的categoryList
* 2. 转化categoryList为list类型
* 3. 在categoryList中add一个当前的category
* 4. 再次转换categoryList为json,并存入redis中
*/
// 直接使用redis删除缓存即可,用户端在查询的时候会直接查库,再把最新的数据放入到缓存中
redis.del(REDIS_ALL_CATEGORY);
}
}
@Transactional
@Override
public void modifyCategory(Category category) {
int result = categoryMapper.updateByPrimaryKey(category);
if (result != 1) {
GraceException.display(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
}
// 直接使用redis删除缓存即可,用户端在查询的时候会直接查库,再把最新的数据放入到缓存中
redis.del(REDIS_ALL_CATEGORY);
}
@Override
public boolean queryCatIsExist(String catName, String oldCatName) {
Example example = new Example(Category.class);
Example.Criteria catCriteria = example.createCriteria();
catCriteria.andEqualTo("name", catName);
if (StringUtils.isNotBlank(oldCatName)) {
catCriteria.andNotEqualTo("name", oldCatName);
}
List<Category> catList = categoryMapper.selectByExample(example);
boolean isExist = false;
if (catList != null && !catList.isEmpty() && catList.size() > 0) {
isExist = true;
}
return isExist;
}
@Override
public List<Category> queryCategoryList() {
return categoryMapper.selectAll();
}
}
service-admin com/imooc/admin/mapper/CategoryMapper.java
package com.imooc.admin.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Category;
import org.springframework.stereotype.Repository;
@Repository
public interface CategoryMapper extends MyMapper<Category> {
}
service-admin resources/mapper/CategoryMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.admin.mapper.CategoryMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.Category" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="tag_color" property="tagColor" jdbcType="VARCHAR" />
</resultMap>
</mapper>
service-api com/imooc/api/BaseController.java
public abstract class BaseController {
@Autowired
public RedisOperator redis;
public static final String MOBILE_SMSCODE = "mobile:smscode";
public static final String REDIS_USER_TOKEN = "redis_user_token";//ctrl+shift+u直接大写
public static final String REDIS_USER_INFO = "redis_user_info";//ctrl+shift+u直接大写
public static final String REDIS_ADMIN_TOKEN = "redis_admin_token";//ctrl+shift+u直接大写
public static final String REDIS_ALL_CATEGORY = "redis_all_category";
public static final String REDIS_WRITER_FANS_COUNTS = "redis_writer_fans_counts";
public static final String REDIS_MY_FOLLOW_COUNTS = "redis_my_follow_counts";
public static final String REDIS_ARTICLE_READ_COUNTS = "redis_article_read_counts";
public static final String REDIS_ALREADY_READ = "redis_already_read";
public static final String REDIS_ARTICLE_COMMENT_COUNTS = "redis_article_comment_counts";
@Value("${website.domain-name}")
public String DOMAIN_NAME;
public static final Integer COOKIE_MONTH = 30 * 24 * 60 * 60;
public static final Integer COOKIE_DELETE = 0;
public static final Integer COMMON_START_PAGE = 1;
public static final Integer COMMON_PAGE_SIZE = 10;
}...
dev-model com/imooc/pojo/Category.java
package com.imooc.pojo;
import javax.persistence.Column;
import javax.persistence.Id;
public class Category {
@Id
private Integer id;
/**
* 分类名,比如:科技,人文,历史,汽车等等
*/
private String name;
/**
* 标签颜色
*/
@Column(name = "tag_color")
private String tagColor;
}Getter + Setter
查询用户列表_设置时间日期转换配置 【用户管理】
service-api com/imooc/api/controller/user/AppUserMngControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Date;
@Api(value = "用户管理相关的接口定义",tags = {"用户管理相关功能的controller"})
@RequestMapping("appUser")
public interface AppUserMngControllerApi {
@ApiOperation(value = "查询所有网站用户",notes = "查询所有网站用户",httpMethod = "POST")
@PostMapping("queryAll")
public GraceJSONResult queryAll(@RequestParam String nickname,
@RequestParam Integer status,
@RequestParam Date startDate,
@RequestParam Date endDate,
@RequestParam Integer page,
@RequestParam Integer pageSize);
}
service-user com/imooc/user/controller/AppUserMngController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.AppUserMngControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.RedisOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class AppUserMngController extends BaseController implements AppUserMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(AppUserMngController.class);
// 字符串无法直接转换成Date类型 需要工具类转换 DateConverterConfig com/imooc/api/config/DateConverterConfig.java
@Override
public GraceJSONResult queryAll(String nickname, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize) {
System.out.println(startDate);
System.out.println(endDate);
if (page == null){
page = COMMON_START_PAGE;
}
if (pageSize == null){
pageSize = COMMON_PAGE_SIZE;
}
return GraceJSONResult.ok();
}
}
// http://admin.imoocnews.com:9090/imooc-news/admin/userList.html
service-api com/imooc/api/config/DateConverterConfig.java
package com.imooc.api.config;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 请求路径url中的参数进行时间日期类型的转换,字符串->日期Date
*/
@Configuration
public class DateConverterConfig implements Converter<String, Date> {
private static final List<String> formatterList = new ArrayList<>(4);
static{
formatterList.add("yyyy-MM");
formatterList.add("yyyy-MM-dd");
formatterList.add("yyyy-MM-dd hh:mm");
formatterList.add("yyyy-MM-dd hh:mm:ss");
}
@Override
public Date convert(String source) {
String value = source.trim();
if ("".equals(value)) {
return null;
}
if(source.matches("^\\d{4}-\\d{1,2}$")){
return parseDate(source, formatterList.get(0));
}else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")){
return parseDate(source, formatterList.get(1));
}else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")){
return parseDate(source, formatterList.get(2));
}else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")){
return parseDate(source, formatterList.get(3));
}else {
GraceException.display(ResponseStatusEnum.SYSTEM_DATE_PARSER_ERROR);
}
return null;
}
/**
* 日期转换方法
* @param dateStr
* @param formatter
* @return
*/
public Date parseDate(String dateStr, String formatter) {
Date date=null;
try {
DateFormat dateFormat = new SimpleDateFormat(formatter);
date = dateFormat.parse(dateStr);
} catch (Exception e) {
e.printStackTrace();
}
return date;
}
}
查询用户列表_实现service与联调 【用户管理】
service-user com/imooc/user/service/AppUserMngService.java
package com.imooc.user.service;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
public interface AppUserMngService {
/**
* 查询管理员列表
* @param nickname
* @param status
* @param startDate
* @param endDate
* @param page
* @param pageSize
* @return
*/
public PagedGridResult queryAllUserList(String nickname, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize);
}
service-api com/imooc/api/service/BaseService.java
package com.imooc.api.service;
import com.github.pagehelper.PageInfo;
import com.imooc.utils.PagedGridResult;
import java.util.List;
public class BaseService {
public PagedGridResult setterPagedGrid(List<?> list, Integer page){ //类型是? 后期不确定是什么泛型
PageInfo<?> pageList = new PageInfo<>(list);
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(list);
gridResult.setPage(page);
gridResult.setRecords(pageList.getTotal());
gridResult.setTotal(pageList.getPages());
return gridResult;
}
}
service-user com/imooc/user/service/impl/AppUserMngServiceImpl.java
package com.imooc.user.service.impl;
import com.github.pagehelper.PageHelper;
import com.imooc.api.service.BaseService;
import com.imooc.enums.Sex;
import com.imooc.enums.UserStatus;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.user.mapper.AppUserMapper;
import com.imooc.user.service.AppUserMngService;
import com.imooc.user.service.UserService;
import com.imooc.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.n3r.idworker.Sid;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
public class AppUserMngServiceImpl extends BaseService implements AppUserMngService {
@Autowired
public AppUserMapper appUserMapper;
@Override
public PagedGridResult queryAllUserList(String nickname, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize) {
Example example = new Example(AppUser.class);
example.orderBy("createdTime").desc();
Example.Criteria criteria = example.createCriteria();
if (StringUtils.isNotBlank(nickname)) {
criteria.andLike("nickname", "%" + nickname + "%");
}
if (UserStatus.isUserStatusValid(status)){
criteria.andEqualTo("activeStatus", status); //对比状态
}
if (startDate != null){
criteria.andGreaterThanOrEqualTo("createdTime", startDate);//数据库和传入参数对比
}
if (endDate != null){
criteria.andLessThanOrEqualTo("endTime", endDate);//数据库和传入参数对比
}
PageHelper.startPage(page, pageSize);
List<AppUser> list = appUserMapper.selectByExample(example);
return setterPagedGrid(list,page);
}
}
查询用户账户_冻结与解封 【用户管理】
service-api com/imooc/api/controller/user/AppUserMngControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Date;
@Api(value = "用户管理相关的接口定义",tags = {"用户管理相关功能的controller"})
@RequestMapping("appUser")
public interface AppUserMngControllerApi {
@ApiOperation(value = "查询所有网站用户",notes = "查询所有网站用户",httpMethod = "POST")
@PostMapping("queryAll")
public GraceJSONResult queryAll(@RequestParam String nickname,
@RequestParam Integer status,
@RequestParam Date startDate,
@RequestParam Date endDate,
@RequestParam Integer page,
@RequestParam Integer pageSize);
@ApiOperation(value = "查看用户详情",notes = "查看用户详情",httpMethod = "POST")
@PostMapping("userDetail")
public GraceJSONResult userDetail(@RequestParam String userId);
@ApiOperation(value = "冻结用户或者解冻用户",notes = "冻结用户或者解冻用户",httpMethod = "POST")
@PostMapping("freezeUserOrNot")
public GraceJSONResult freezeUserOrNot(@RequestParam String userId,@RequestParam Integer doStatus);
}
service-user com/imooc/user/controller/AppUserMngController.java
@RestController
public class AppUserMngController extends BaseController implements AppUserMngControllerApi {
final static Logger logger = LoggerFactory.getLogger(AppUserMngController.class);
@Autowired
private AppUserMngService appUserMngService;
@Autowired
private UserService userService;
......
@Override
public GraceJSONResult freezeUserOrNot(String userId, Integer doStatus) {
if (!UserStatus.isUserStatusValid(doStatus)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_STATUS_ERROR);
}
appUserMngService.freezeUserOrNot(userId, doStatus);
//若冻结后 用户处于登录状态 还可以进行操作 所以要刷新用户状态
//方法①:删除用户会话,从而保证用户需要重新登陆以后再来刷新她的会话状态
redis.del(REDIS_USER_INFO + ":" + userId);
//方法②:查询最新用户的信息,重新放入redis中,做一次更新
return GraceJSONResult.ok();
}
service-user com/imooc/user/service/AppUserMngService.java
package com.imooc.user.service;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
public interface AppUserMngService {
/**
* 查询管理员列表
*/
public PagedGridResult queryAllUserList(String nickname, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize);
/**
* 冻结用户账号或者解除冻结
*/
public void freezeUserOrNot(String userId, Integer doStatus);
}
service-user com/imooc/user/service/impl/AppUserMngServiceImpl.java
@Transactional
@Override
public void freezeUserOrNot(String userId, Integer doStatus) {
AppUser user = new AppUser();
user.setId(userId);
user.setActiveStatus(doStatus);
appUserMapper.updateByPrimaryKeySelective(user);
}
梳理文章article表结构 【文章服务】
- 构建文章服务
- 作者中心发表文章
- 作者中心内容管理
- 自动审核
[阿里客户端],手动审核
构建文章服务工程 【文章服务】
新创建一个Module
GroupId:com.imooc
ArtifactId:imooc-news-dev-service-article
pom参考service-admin移植 resources里的所有文件(除mapper)也要移植
service-article 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>
<parent>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>imooc-news-dev-service-article</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
resources logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<!-- <property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/service-article"/>-->
<property name="LOG_HOME" value="C:/Users/Pluminary/Desktop/imooc-news-article"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/service-article.%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>
<!--<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--</logger>-->
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
service-article com/imooc/article/Application.java
package com.imooc.article;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
service-article com/imooc/article/controller/HelloController.java
package com.imooc.article.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello() {
return GraceJSONResult.ok();
}
}
========================================================================
http://localhost:8001/hello
{
"status": 200,
"msg": "操作成功!",
"success": true,
"data": null
}
service-article application-dev
server:
port: 8001
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
## setup CN from java, This is resource
website:
domain-name: imoocnews.com
## open mybatis log in dev
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
############################################################
#
# admin用户微服务
# web访问端口号 约定:8001
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-article
datasource: # 数据源的相关配置
type: com.zaxxer.hikari.HikariDataSource # 数据源类型:HikariCP
driver-class-name: org.mariadb.jdbc.Driver # mysql驱动
url: jdbc:mysql://localhost:3306/imooc-news-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&sessionVariables=tx_isolation='READ-COMMITTED'
username: root
password: root
hikari:
connection-timeout: 30000 # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
minimum-idle: 5 # 最小连接数
maximum-pool-size: 20 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
pool-name: DateSourceHikariCP # 连接池名字
max-lifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
connection-test-query: SELECT 1
data-source-properties:
tx_isolation: 'READ-COMMITTED'
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
data:
mongodb:
uri: mongodb://root:root@192.168.170.135:27017
database: imooc-news
############################################################
#
# mybatis 配置
#
############################################################
mybatis:
type-aliases-package: com.imooc.pojo # 所有POJO类所在包路径
mapper-locations: classpath:mapper/*.xml # mapper映射文件
############################################################
#
# mybatis mapper 配置
#
############################################################
# 通用 Mapper 配置
mapper:
mappers: com.imooc.my.mapper.MyMapper
not-empty: false # 在进行数据库操作的的时候,判断表达式 username != null, 是否追加 username != ''
identity: MYSQL
# 分页插件配置
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
summernote与多文件上传需求 【发头条】
【前端工程里面的】createArticle.html
...
<script src="libs/vue.min.js"></script>
<script src="libs/axios.min.js"></script>
<link href="./libs/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script src="./libs/jquery-3.4.1.min.js"></script>
<script src="libs/layDate-v5.0.9/laydate/laydate.js"></script>
<script src="./libs/bootstrap/js/bootstrap.min.js"></script>
<link href="./libs/summernote/dist/summernote.css" rel="stylesheet">
<script src="./libs/summernote/dist/summernote.js"></script>
<!-- 中文汉化 -->
<script src="libs/summernote/lang/summernote-zh-CN.js"></script>
<script src="js/app.js"></script>
<script type="text/javascript">
......
<!-- 富文本编辑器 -->
<div id="editor2" class="editor-container">
<div class="article-title-wrapper">
<input id="title" class="article-title" placeholder="请输入文字标题(6-30长度)" v-model="articleTitle" maxlength="30"/>
</div>
<div class="article-content-wrapper">
<div id="summernote" class="summernote"></div>
</div>
<div class="other-info">
<div class="cover-wrapper">
<div class="cover">文章领域</div>
<div class="choose-type">
<!-- <select v-model="articleCategory">
<option value="0">请选择</option>
<option value="1">汽车</option>
<option value="2">科技</option>
<option value="3">历史</option>
</select> -->
<select v-model="articleCategory">
<option :value="cat.id" v-for="(cat, index) in catList" v-key="index">{{cat.name}}</option>
</select>
</div>
</div>
<div class="cover-wrapper">
<div class="cover">文章封面</div>
<div class="choose-type">
<div><input type="radio" name="articleType" v-model="articleType" value="1" checked/><span class="choose-words">单封面</span></div>
<div style="margin-left: 30px;"><input type="radio" v-model="articleType" value="2" name="articleType"/><span class="choose-words">无封面</span></div>
</div>
</div>
<div class="cover-wrapper" v-show="articleType==1">
<div class="cover"></div>
<div class="choose-cover">
<div class="uploader-comp">
<div id="block-choose" class="block-choose" :style="coverStyle">
<img src="./img/icon-go-upload.png" style="width: 20px; height: 20px; align-self: center;" v-show="articleCover == '' || articleCover == null"/>
</div>
<input type="file" @change="uploadCover" @mouseover="mouseOver" @mouseout="mouseOut" id="inputPic" class="inputPic" accept="image/jpeg,image/jpg,image/png">
</div>
<div style="margin-top: 10px; color: #9b9d9e;">请上传JPG、JPEG、PNG格式的封面图噢~</div>
</div>
</div>
</div>
<div class="publish-bottom">
<div class="buttons">
<button class="white-btn" type="button" @click="goBack">返回</button>
<button class="white-btn" type="button" @click="preview">预览</button>
<!-- <button class="white-btn" type="button" @click="save">保存草稿</button> -->
<!-- FIXME: 计算剩余时间,使用RMQ延时队列,或分布式定时任务 -->
<button class="white-btn" type="button" @click="doTiming">{{appointWords}}</button>
<input type="text" class="timing-date-picker" placeholder="定时日期" id="choose-date" v-show="isAppoint==1" readonly>
<button class="red-btn" type="button" @click="publish">发布文章</button>
</div>
</div>
</div>
</div>
......
// 初始化编辑器
$('#summernote').summernote({
placeholder: '请输入正文...',
lang: 'zh-CN',
height: 600,
width: 800,
border: 0,
// disableDragAndDrop: true, // 禁止文件拖放
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture']],
['view', ['fullscreen', 'codeview', 'help']]
],
实现多文件上传uploadSomeFiles 【发头条】
service-api com/imooc/api/controller/files/FileUploaderControllerApi.java
package com.imooc.api.controller.files;
@Api(value = "文件上传的controller",tags = {"xx功能的Controller"})
@RequestMapping("fs")
public interface FileUploaderControllerApi {
/**
* 上传单文件
* @param userId
* @param file
* @return
* @throws Exception
*/
@ApiOperation(value = "上传用户头像",notes = "上传用户头像",httpMethod = "POST")
@PostMapping("/uploadFace")
public GraceJSONResult uploadFace(@RequestParam String userId, MultipartFile file) throws Exception;
/**
* 上传多文件
* @param userId
* @param files
* @return
* @throws Exception
*/
@ApiOperation(value = "上传用户头像",notes = "上传用户头像",httpMethod = "POST")
@PostMapping("/uploadSomeFiles") //因为前端createArticle.html 178行 multiForm.append('files',f,f.name);
public GraceJSONResult uploadSomeFiles(@RequestParam String userId, MultipartFile[] files) throws Exception;
......
}
service-file com/imooc/files/controller/FileUploaderController.java
......
@Override
public GraceJSONResult uploadSomeFiles(String userId, MultipartFile[] files) throws Exception {
// 声明一个list,用于存放多个图片的地址路径,返回到前端
List<String> imageUrlList = new ArrayList<>();
if (files != null && files.length > 0){
for (MultipartFile file: files){
String path = "";
if (file != null){
// 获得文件上传的名称
String fileName = file.getOriginalFilename();
//判断文件名不能为空
if (StringUtils.isNotBlank(fileName)){
String fileNameArr[] = fileName.split("\\.");
//获得后缀名
String suffix = fileNameArr[fileNameArr.length - 1];
//防止黑客上传文件攻击服务器 判断后缀符合我们的预定义规范
if (!suffix.equalsIgnoreCase("png") &&
!suffix.equalsIgnoreCase("jpg") &&
!suffix.equalsIgnoreCase("jpeg")
){
continue;
}
// fdfs执行上传 要让外面得以访问 ①需要把内网的环境发布到公网 [内网穿透] ②路由器端口映射到外网 ③fastdfs安装到公网里
// path = uploaderService.uploadFdfs(file, suffix);
// OSS执行上传
path = uploaderService.uploadOSS(file, userId, suffix);
}else {
continue;
}
}else {
continue;
}
String finalPath = "";
if (StringUtils.isNotBlank(path)){
// finalPath = fileResource.getHost() + path;
finalPath = fileResource.getOssHost() + path;
// FIXME: 放入到imageUrlList之前,需要对图片做一次审核 [doAliImageReview]
imageUrlList.add(finalPath);
} else{
continue;
}
// return GraceJSONResult.ok(finalPath);
// return GraceJSONResult.ok(doAliImageReview(finalPath)); //这里加了图片审核咯
}
}
return GraceJSONResult.ok(imageUrlList);
}
......
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
//【增加拦截uploadSomeFiles】
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UserActiveInterceptor userActiveInterceptor() {
return new UserActiveInterceptor();
}
@Bean
public AdminTokenInterceptor adminTokenInterceptor() {
return new AdminTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace")
.addPathPatterns("/fs/uploadSomeFiles");
registry.addInterceptor(adminTokenInterceptor())//继续添加拦截器:查询admin列表 创建新admin用户
.addPathPatterns("/adminMng/adminIsExist")
.addPathPatterns("/adminMng/addNewAdmin")
.addPathPatterns("/adminMng/getAdminList")
.addPathPatterns("/fs/uploadToGridFS")
.addPathPatterns("/friendLinkMng/saveOrUpdateFriendLink")
.addPathPatterns("/friendLinkMng/getFriendLinkList")
.addPathPatterns("/friendLinkMng/delete")
.addPathPatterns("/categoryMng/saveOrUpdateCategory")
.addPathPatterns("/categoryMng/getCatList");
registry.addInterceptor(userActiveInterceptor())
.addPathPatterns("/fs/uploadSomeFiles");
}
}
获得列表_业务接口解耦与Redis缓存应用 【文章领域】
getCatList 和 getCats 一个是用户端一个是admin 业务体系不一样 所以同样是查询分类列表
但是还是应该拆开 使耦合减少 得到高效解耦
查询放在Redis里面 效率变高媒体号作家中心 | 发文章 (imoocnews.com)
刷新一下 文章领域就可以找到那些分类
Redis里面会有信息 redis_all_category
[{“id”:2,”name”:”汽车”,”tagColor”:”#8939bd”},{“id”:3,”name”:”娱乐”,”tagColor”:”#c939aa”},{“id”:5,”name”:”地理”,”tagColor”:”#57394a”},{“id”:6,”name”:”历史”,”tagColor”:”#29ab4a”},{“id”:7,”name”:”科技”,”tagColor”:”#2467bc”},{“id”:9,”name”:”体育”,”tagColor”:”#c98f4a”},{“id”:10,”name”:”搞笑”,”tagColor”:”#68b84a”},{“id”:11,”name”:”技术”,”tagColor”:”#c9394a”},{“id”:12,”name”:”慕课”,”tagColor”:”#682aa8”},{“id”:13,”name”:”技能”,”tagColor”:”#c9394a”},{“id”:14,”name”:”课网”,”tagColor”:”#c9a24a”}]
service-api com/imooc/api/controller/admin/CategoryMngControllerApi.java
// 【getCasts】
package com.imooc.api.controller.admin;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.SaveCatrgoryBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Api(value = "文章分类维护", tags = {"文章分类维护controller"})
@RequestMapping("categoryMng")
public interface CategoryMngControllerApi {
@PostMapping("saveOrUpdateCategory")
@ApiOperation(value = "新增或修改分类", notes = "新增或修改分类", httpMethod = "POST")
public GraceJSONResult saveOrUpdateCategory(@RequestBody @Valid SaveCatrgoryBO saveCatrgoryBO,
BindingResult result);
@PostMapping("getCatList")
@ApiOperation(value = "查询分类列表", notes = "查询分类列表", httpMethod = "POST")
public GraceJSONResult getCatList();
@GetMapping("getCats")
@ApiOperation(value = "用户端查询分类列表", notes = "用户端查询分类列表", httpMethod = "GET")
public GraceJSONResult getCats();
}
service-admin com/imooc/admin/controller/CategoryMngController.java
@Override
public GraceJSONResult getCats() {
// 先从redis中查询,如果有,则返回,如果没有,则查询数据库库后先放缓存,放返回
String allCatJson = redis.get(REDIS_ALL_CATEGORY);
List<Category> categoryList = null;
if (StringUtils.isBlank(allCatJson)) {
categoryList = categoryService.queryCategoryList();
redis.set(REDIS_ALL_CATEGORY, JsonUtils.objectToJson(categoryList));
} else {
categoryList = JsonUtils.jsonToList(allCatJson, Category.class);
}
return GraceJSONResult.ok(categoryList);
}
admin端维护数据缓存 【文章领域】
文章分类 | 运营管理平台 (imoocnews.com)
在管理员修改文章类型后 【课网 → 课课】
媒体号作家中心 | 发文章 (imoocnews.com)
回到用户发文章的文章领域类型也会一起修改
慕课新闻 | 风间影月 (imoocnews.com)
同时首页上方的栏目框也会修改
service-admin com/imooc/admin/service/impl/CategoryServiceImpl.java
@Service
public class CategoryServiceImpl extends BaseService implements CategoryService {
@Autowired
public CategoryMapper categoryMapper;
@Transactional
@Override
public void createCategory(Category category) {
// 分类不会很多,所以id不需要自增,这个表的数据也不会多到几万甚至分表,数据都会集中在一起
int result = categoryMapper.insert(category);
if (result != 1){
GraceException.display(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
/**
* 不建议如下做法:
* 1. 查询redis中的categoryList
* 2. 转化categoryList为list类型
* 3. 在categoryList中add一个当前的category
* 4. 再次转换categoryList为json,并存入redis中
*/
// 直接使用redis删除缓存即可,用户端在查询的时候会直接查库,再把最新的数据放入到缓存中
redis.del(REDIS_ALL_CATEGORY);
}
}
@Transactional
@Override
public void modifyCategory(Category category) {
int result = categoryMapper.updateByPrimaryKey(category);
if (result != 1) {
GraceException.display(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
}
// 直接使用redis删除缓存即可,用户端在查询的时候会直接查库,再把最新的数据放入到缓存中
redis.del(REDIS_ALL_CATEGORY);
}
......
发布文章入库Controller及验证【发头条】
service-api com/imooc/api/controller/article/ArticleControllerApi.java
package com.imooc.api.controller.article;
@Api(value = "文章业务的controller", tags = {"文章业务的controller"})
@RequestMapping("article")
public interface ArticleControllerApi {
@PostMapping("createArticle")
@ApiOperation(value = "用户发文", notes = "用户发文", httpMethod = "POST")
public GraceJSONResult createArticle(@RequestBody @Valid NewArticleBO newArticleBO, BindingResult result);
}
service-article com/imooc/article/controller/ArticleController.java
package com.imooc.article.controller;
@RestController
public class ArticleController extends BaseController implements ArticleControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticleController.class);
@Override
public GraceJSONResult createArticle(NewArticleBO newArticleBO, BindingResult result) {
if (result.hasErrors()){
// 判断BindingResult是否保存错误的验证信息,如果有,则直接return
Map<String, String> errorMap = getErrors(result);
return GraceJSONResult.errorMap(errorMap);
}
// 判断文章封面类型,单图必填,纯文字则设置为空
if (newArticleBO.getArticleType() == ArticleCoverType.ONE_IMAGE.type){
if (StringUtils.isBlank(newArticleBO.getArticleCover())){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_CATEGORY_NOT_EXIST_ERROR);
}
} else if (newArticleBO.getArticleType() == ArticleCoverType.WORDS.type) {
newArticleBO.setArticleCover("");
}
// 判断分类id是否存在
String allCatJson = redis.get(REDIS_ALL_CATEGORY);
Category temp = null;
if (StringUtils.isBlank(allCatJson)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
} else {
List<Category> catList =
JsonUtils.jsonToList(allCatJson, Category.class);
for (Category c : catList) {
if(c.getId() == newArticleBO.getCategoryId()) {
temp = c;
break;
}
}
if (temp == null) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_CATEGORY_NOT_EXIST_ERROR);
}
}
return GraceJSONResult.ok();
}
}
http://writer.imoocnews.com:9090/imooc-news/writer/createArticle.html
dev-model com/imooc/pojo/bo/NewArticleBO.java
package com.imooc.pojo.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* 用户发文的BO
*/
public class NewArticleBO {
@NotBlank(message = "文章标题不能为空")
@Length(max = 30, message = "文章标题长度不能超过30")
private String title;
@NotBlank(message = "文章内容不能为空")
@Length(max = 9999, message = "文章内容长度不能超过10000")
private String content;
@NotNull(message = "请选择文章领域")
private Integer categoryId;
@NotNull(message = "请选择正确的文章封面类型")
@Min(value = 1, message = "请选择正确的文章封面类型")
@Max(value = 2, message = "请选择正确的文章封面类型")
private Integer articleType;
private String articleCover;
@NotNull(message = "文章发布类型不正确")
@Min(value = 0, message = "文章发布类型不正确")
@Max(value = 1, message = "文章发布类型不正确")
private Integer isAppoint;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") // 前端日期字符串传到后端后,转换为Date类型
private Date publishTime;
@NotBlank(message = "用户未登录")
private String publishUserId;
}Getter + Setter
发布文章入库Service及联调【也可以定时发布】
http://writer.imoocnews.com:9090/imooc-news/writer/contentMng.html
发布完成后去数据库article中就会存在数据了
generator-datebase generatorConfig-article.xml [逆向生成]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 通用mapper所在目录 -->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.imooc.my.mapper.MyMapper"/>
</plugin>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/imooc-news-dev"
userId="root"
password="root">
</jdbcConnection>
<!-- 对应生成的pojo所在包 -->
<javaModelGenerator targetPackage="com.imooc.pojo" targetProject="mybatis-generator-database/src/main/java"/>
<!-- 对应生成的mapper所在目录 -->
<sqlMapGenerator targetPackage="mapper.article" targetProject="mybatis-generator-database/src/main/resources"/>
<!-- 配置mapper对应的java映射 -->
<javaClientGenerator targetPackage="com.imooc.article.mapper" targetProject="mybatis-generator-database/src/main/java" type="XMLMAPPER"/>
<!-- 数据库表 -->
<table tableName="comments"></table>
</context>
</generatorConfiguration>
generator-datebase com/imooc/mybatis/utils/ArticleGenerator.java
//[运行时候就会自动生成对应文件 目录是上面的generatorConfig-article.xml]
package com.imooc.mybatis.utils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ArticleGenerator {
public void generator() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("mybatis-generator-database"
+ File.separator
+ "generatorConfig-article.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
ArticleGenerator generatorSqlmap = new ArticleGenerator();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
service-article mapper/ArticleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.ArticleMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.Article" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="title" property="title" jdbcType="VARCHAR" />
<result column="category_id" property="categoryId" jdbcType="INTEGER" />
<result column="article_type" property="articleType" jdbcType="INTEGER" />
<result column="article_cover" property="articleCover" jdbcType="VARCHAR" />
<result column="is_appoint" property="isAppoint" jdbcType="INTEGER" />
<result column="article_status" property="articleStatus" jdbcType="INTEGER" />
<result column="publish_user_id" property="publishUserId" jdbcType="VARCHAR" />
<result column="publish_time" property="publishTime" jdbcType="TIMESTAMP" />
<result column="read_counts" property="readCounts" jdbcType="INTEGER" />
<result column="comment_counts" property="commentCounts" jdbcType="INTEGER" />
<result column="mongo_file_id" property="mongoFileId" jdbcType="VARCHAR" />
<result column="is_delete" property="isDelete" jdbcType="INTEGER" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
<result column="content" property="content" jdbcType="LONGVARCHAR" />
</resultMap>
</mapper>
service-article com/imooc/article/service/ArticleService.java
package com.imooc.article.service;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import java.util.List;
public interface ArticleService {
/**
* 发布文章
*/
public void createArticle(NewArticleBO newArticleBO, Category category);
}
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
package com.imooc.article.service.impl;
import com.imooc.api.service.BaseService;
import com.imooc.article.mapper.ArticleMapper;
import com.imooc.article.service.ArticleService;
import com.imooc.enums.ArticleAppointType;
import com.imooc.enums.ArticleReviewStatus;
import com.imooc.enums.YesOrNo;
import com.imooc.exception.GraceException;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.Article;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import com.imooc.utils.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.n3r.idworker.Sid;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
import static com.imooc.api.BaseController.REDIS_ALL_CATEGORY;
@Service
public class ArticleServiceImpl extends BaseService implements ArticleService {
@Autowired
private ArticleMapper articleMapper; //红色波浪线就去ArticleMapper上面加@Repository
@Autowired
private Sid sid;
@Transactional
@Override
public void createArticle(NewArticleBO newArticleBO, Category category) {
String articleId = sid.nextShort();
Article article = new Article();
BeanUtils.copyProperties(newArticleBO, article);
article.setId(articleId);
article.setCategoryId(category.getId());
article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
article.setCommentCounts(0);
article.setReadCounts(0);
article.setIsDelete(YesOrNo.NO.type);
article.setCreateTime(new Date());
article.setUpdateTime(new Date());
if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
article.setPublishTime(newArticleBO.getPublishTime()); //用户可以在前端选择定时发布
} else if (article.getIsAppoint() == ArticleAppointType.IMMEDIATELY.type) {
article.setPublishTime(new Date());
}
int res = articleMapper.insert(article);
if (res != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
}
}
}
dev-model com/imooc/pojo/Article.java
package com.imooc.pojo;
import javax.persistence.Column;
import javax.persistence.Id;
import java.util.Date;
public class Article {
@Id
private String id;
/**
* 文章标题
*/
private String title;
/**
* 文章所属分类id
*/
@Column(name = "category_id")
private Integer categoryId;
/**
* 文章类型,1:图文(1张封面),2:纯文字
*/
@Column(name = "article_type")
private Integer articleType;
/**
* 文章封面图,article_type=1 的时候展示
*/
@Column(name = "article_cover")
private String articleCover;
/**
* 是否是预约定时发布的文章,1:预约(定时)发布,0:即时发布 在预约时间到点的时候,把1改为0,则发布
*/
@Column(name = "is_appoint")
private Integer isAppoint;
/**
* 文章状态,1:审核中(用户已提交),2:机审结束,等待人工审核,3:审核通过(已发布),4:审核未通过;5:文章撤回(已发布的情况下才能撤回和删除)
*/
@Column(name = "article_status")
private Integer articleStatus;
/**
* 发布者用户id
*/
@Column(name = "publish_user_id")
private String publishUserId;
/**
* 文章发布时间(也是预约发布的时间)
*/
@Column(name = "publish_time")
private Date publishTime;
/**
* 用户累计点击阅读数(喜欢数)(点赞) - 放redis
*/
@Column(name = "read_counts")
private Integer readCounts;
/**
* 文章评论总数。评论防刷,距离上次评论需要间隔时间控制几秒
*/
@Column(name = "comment_counts")
private Integer commentCounts;
@Column(name = "mongo_file_id")
private String mongoFileId;
/**
* 逻辑删除状态,非物理删除,1:删除,0:未删除
*/
@Column(name = "is_delete")
private Integer isDelete;
/**
* 文章的创建时间
*/
@Column(name = "create_time")
private Date createTime;
/**
* 文章的修改时间
*/
@Column(name = "update_time")
private Date updateTime;
/**
* 文章内容,长度不超过9999,需要在前后端判断
*/
private String content;
构建定时任务 定时发布文章【定时任务】
service-article com/imooc/article/task/TaskPublishArticles.java
package com.imooc.article.task;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
@Configuration // 1.标记配置类,使得springboot容器扫描到
@EnableScheduling // 2.开启定时任务
public class TaskPublishArticles {
@Scheduled(cron = "0/3 * * * * ? ")
private void publishArticles(){
System.out.println("执行定时任务:" + LocalDateTime.now());
}
}
=================================================================
执行定时任务:2024-07-22T14:34:54.009
执行定时任务:2024-07-22T14:34:57.013
执行定时任务:2024-07-22T14:35:00.012
执行定时任务:2024-07-22T14:35:03.002
执行定时任务:2024-07-22T14:35:06.001
执行定时任务:2024-07-22T14:35:09.006
service-article com/imooc/article/task/TaskPublishArticles.java
package com.imooc.article.task;
import com.imooc.article.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
@Configuration // 1.标记配置类,使得springboot容器扫描到
@EnableScheduling // 2.开启定时任务
public class TaskPublishArticles {
@Autowired
private ArticleService articleService;
// 添加定时任务,注明定时任务的表达式
// 【若文章数量庞大 需要RabbitMQ去做优化 后面会讲!】
@Scheduled(cron = "0/3 * * * * ? ")
private void publishArticles(){
System.out.println("执行定时任务:" + LocalDateTime.now());
// 4. 调用文章service,把当前时间应该发布的定时文章,状态改为即时
articleService.updateAppointToPublish();
}
}
service-article com/imooc/article/mapper/ArticleMapperCustom.java
package com.imooc.article.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Article;
import org.springframework.stereotype.Repository;
@Repository
public interface ArticleMapperCustom extends MyMapper<Article> {
public void updateAppointToPublish();
}
service-article resources/mapper/ArticleMapperCustom.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.ArticleMapperCustom" >
<update id="updateAppointToPublish">
update
article
set
is_appoint = 0
where
publish_time <= NOW()
and
is_appoint = 1
</update>
</mapper>
service-article com/imooc/article/service/ArticleService.java
package com.imooc.article.service;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import java.util.List;
public interface ArticleService {
/**
* 发布文章
*/
public void createArticle(NewArticleBO newArticleBO, Category category);
/**
* 更新定时发布为即使发布
*/
public void updateAppointToPublish();
}
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Transactional //添加事务[更新操作]
@Override
public void updateAppointToPublish() {
articleMapperCustom.updateAppointToPublish();
}
文章列表展示 【内容管理】
[mybatis中关于example类详解mybatis的Example
Criteria]的使用 - 万事俱备就差个程序员 - 博客园 (cnblogs.com)
service-api com/imooc/api/controller/article/ArticleControllerApi.java
@Api(value = "文章业务的controller", tags = {"文章业务的controller"})
@RequestMapping("article")
public interface ArticleControllerApi {
@PostMapping("createArticle")
@ApiOperation(value = "用户发文", notes = "用户发文", httpMethod = "POST")
public GraceJSONResult createArticle(@RequestBody @Valid NewArticleBO newArticleBO, BindingResult result);
@PostMapping("queryMyList") //对应着前端contentMng.html 340行
@ApiOperation(value = "查询用户的所有文章列表", notes = "查询用户的所有文章列表", httpMethod = "POST")
public GraceJSONResult queryMyList(@RequestParam String userId,
@RequestParam String keyword,
@RequestParam Integer status,
@RequestParam Date startDate,
@RequestParam Date endDate,
@RequestParam Integer page,
@RequestParam Integer pageSize);
}
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult queryMyList(String userId, String keyword, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize) {
if (StringUtils.isBlank(userId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_QUERY_PARAMS_ERROR);
}
if (page == null){
page = COMMON_START_PAGE;
}
if (pageSize == null){
pageSize = COMMON_PAGE_SIZE;
}
// 查询我的列表,调用service
PagedGridResult grid = articleService.queryMyArticleList(userId, keyword, status, startDate, endDate, page, pageSize);
return GraceJSONResult.ok(grid);
}
=========================================================================
http://writer.imoocnews.com:9090/imooc-news/writer/contentMng.html
service-article com/imooc/article/service/ArticleService.java
package com.imooc.article.service;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
import java.util.List;
public interface ArticleService {
/**
* 发布文章
*/
public void createArticle(NewArticleBO newArticleBO, Category category);
/**
* 更新定时发布为即使发布
*/
public void updateAppointToPublish();
/**
* 用户中心-查询我的文章列表
*/
public PagedGridResult queryMyArticleList(String userId, String keyword, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize);
}
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Service
public class ArticleServiceImpl extends BaseService implements ArticleService {
@Autowired
private ArticleMapper articleMapper; //红色波浪线就去ArticleMapper上面加@Repository
@Autowired
private ArticleMapperCustom articleMapperCustom;
@Autowired
private Sid sid;
//匹配到前端的一种显示方法
@Override
public PagedGridResult queryMyArticleList(String userId, String keyword, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize) {
Example example = new Example(Article.class);
example.orderBy("createTime").desc();
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("publishUserId", userId);
if (StringUtils.isNotBlank(keyword)){
//模糊查询
criteria.andLike("title", "%"+keyword+"%");
}
if (ArticleReviewStatus.isArticleStatusValid(status)){
// 有效就匹配 无效就查询所有
criteria.andEqualTo("articleStatus", status);
}
// 12是在前端显示审核中
if (status != null && status == 12){
criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
.orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
}
// 逻辑删除
criteria.andEqualTo("isDelete", YesOrNo.NO.type);
if (startDate != null){ //大于等于
criteria.andGreaterThanOrEqualTo("publishTime", startDate);
}
if (startDate != null){ //小于等于
criteria.andLessThanOrEqualTo("publishTime",endDate);
}
PageHelper.startPage(page, pageSize);
List<Article> list = articleMapper.selectByExample(example);
return setterPagedGrid(list,page);
}
/*
ArticleMapper 可以实现 selectByExample 是因为它继承了 MyMapper 接口,而 MyMapper 提供了一些通用的 CRUD 操作,这些操作包括 selectByExample。
selectByExample 是 MyBatis 提供的一种动态查询方法。它允许你根据条件动态地生成 SQL 查询,而不需要手动编写复杂的 SQL 语句。这在实际开发中非常方便,因为你可以通过构建 Example 对象来动态设置查询条件。
ArticleMapper 继承了 MyMapper<Article>,这意味着它自动获得了 MyMapper 中定义的所有方法,包括 selectByExample。MyMapper 是一个通用的 Mapper 接口,封装了常用的数据库操作方法。
Example 和 Criteria
Example: 用于构建查询条件的对象。在这里,我们创建了一个 Example 对象,用于设置查询的表(Article.class)和排序规则(按 createTime 降序)。
Criteria: 用于添加具体的查询条件。在 Example 对象中创建 Criteria 对象,并使用它来添加各种条件(例如 publishUserId、title、articleStatus、isDelete 等)。
selectByExample
selectByExample 方法使用 Example 对象中的条件动态生成 SQL 查询,并从数据库中获取符合条件的记录。在这个例子中,我们使用了 articleMapper.selectByExample(example) 来根据构建的 Example 对象进行查询。
Example 详细用法
Example 和 Criteria 的使用使得我们可以非常灵活地构建查询条件,而不需要直接拼接 SQL 语句。这不仅提高了代码的可读性,还减少了 SQL 注入的风险。
*/
service-article com/imooc/article/mapper/ArticleMapper.java
package com.imooc.article.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Article;
import org.springframework.stereotype.Repository;
@Repository
public interface ArticleMapper extends MyMapper<Article> {
}
阿里AI文本检测【内容审核】[机器审核]
dev-common pom.xml
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-green</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
dev-common com/imooc/utils/extend/AliTextReviewUtils.java
package com.imooc.utils.extend;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.green.model.v20180509.TextScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.*;
@Component
public class AliTextReviewUtils {
@Autowired
private AliyunResource aliyunResource;
public String reviewTextContent(String content) {
IClientProfile profile = DefaultProfile.getProfile("cn-shanghai",
aliyunResource.getAccessKeyID(),
aliyunResource.getAccessKeySecret());
IAcsClient client = new DefaultAcsClient(profile);
TextScanRequest textScanRequest = new TextScanRequest();
textScanRequest.setAcceptFormat(FormatType.JSON); // 指定api返回格式
textScanRequest.setHttpContentType(FormatType.JSON);
textScanRequest.setMethod(com.aliyuncs.http.MethodType.POST); // 指定请求方法
textScanRequest.setEncoding("UTF-8");
textScanRequest.setRegionId("cn-shanghai");
List<Map<String, Object>> tasks = new ArrayList<Map<String, Object>>();
Map<String, Object> task1 = new LinkedHashMap<String, Object>();
task1.put("dataId", UUID.randomUUID().toString());
/**
* 待检测的文本,长度不超过10000个字符
*/
// 抵制毒品交易
// 尼玛
task1.put("content", content);
tasks.add(task1);
JSONObject data = new JSONObject();
/**
* 检测场景,文本垃圾检测传递:antispam
**/
data.put("scenes", Arrays.asList("antispam"));
data.put("tasks", tasks);
System.out.println(JSON.toJSONString(data, true));
try {
textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
// 请务必设置超时时间
textScanRequest.setConnectTimeout(3000);
textScanRequest.setReadTimeout(6000);
HttpResponse httpResponse = client.doAction(textScanRequest);
if(httpResponse.isSuccess()){
JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
System.out.println(JSON.toJSONString(scrResponse, true));
if (200 == scrResponse.getInteger("code")) {
JSONArray taskResults = scrResponse.getJSONArray("data");
for (Object taskResult : taskResults) {
if(200 == ((JSONObject)taskResult).getInteger("code")){
JSONArray sceneResults = ((JSONObject)taskResult).getJSONArray("results");
JSONObject sceneResult = (JSONObject)sceneResults.get(0);
// for (Object sceneResult : sceneResults) {
String scene = sceneResult.getString("scene");
String suggestion = sceneResult.getString("suggestion");
//根据scene和suggetion做相关处理
//suggestion == pass 未命中垃圾 suggestion == block 命中了垃圾,可以通过label字段查看命中的垃圾分类
System.out.println("args = [" + scene + "]");
System.out.println("args = [" + suggestion + "]");
// suggestion=pass:文本正常,文章状态改为发布通过
// review:需要人工审核,需要在后台管理系统中进行人工审核(很多自媒体平台都会采用机审+人工审的方式)
// block:文本违规,可以直接删除或者做限制处理,审核不通过
// }
return suggestion;
}else{
System.out.println("task process fail:" + ((JSONObject)taskResult).getInteger("code"));
return null;
}
}
} else {
System.out.println("detect not success. code:" + scrResponse.getInteger("code"));
return null;
}
}else{
System.out.println("response not success. status:" + httpResponse.getStatus());
return null;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return null;
}
}
实现阿里AI自动审核文章【内容审核】
【沿用上面的AliTextReviewUtils】
@Service
public class ArticleServiceImpl extends BaseService implements ArticleService {
@Autowired
private ArticleMapper articleMapper; //红色波浪线就去ArticleMapper上面加@Repository
@Autowired
private ArticleMapperCustom articleMapperCustom;
@Autowired
private AliTextReviewUtils aliTextReviewUtils;
@Autowired
private Sid sid;
@Transactional
@Override
public void createArticle(NewArticleBO newArticleBO, Category category) {
String articleId = sid.nextShort();
Article article = new Article();
BeanUtils.copyProperties(newArticleBO, article);
article.setId(articleId);
article.setCategoryId(category.getId());
article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
article.setCommentCounts(0);
article.setReadCounts(0);
article.setIsDelete(YesOrNo.NO.type);
article.setCreateTime(new Date());
article.setUpdateTime(new Date());
if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
article.setPublishTime(newArticleBO.getPublishTime()); //用户可以在前端选择定时发布
} else if (article.getIsAppoint() == ArticleAppointType.IMMEDIATELY.type) {
article.setPublishTime(new Date());
}
int res = articleMapper.insert(article);
if (res != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
}
/**
* FIXME: 我们只检测正常的词汇,非正常词汇大家课后去检测
*/
// 通过阿里智能AI实现对文章文本的自动检测(自动审核)
// String reviewTextResult = aliTextReviewUtils.reviewTextContent(newArticleBO.getContent());
String reviewTextResult = ArticleReviewLevel.REVIEW.type;
if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.PASS.type)) {
// 修改当前的文章,状态标记为审核通过
this.updateArticleStatus(articleId, ArticleReviewStatus.SUCCESS.type);
} else if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.REVIEW.type)) {
// 修改当前的文章,状态标记为需要人工审核
this.updateArticleStatus(articleId, ArticleReviewStatus.WAITING_MANUAL.type);
} else if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.BLOCK.type)) {
// 修改当前的文章,状态标记为审核未通过
this.updateArticleStatus(articleId, ArticleReviewStatus.FAILED.type);
}
}
......
......
@Transactional
@Override
public void updateArticleStatus(String articleId, Integer pendingStatus) {
Example example = new Example(Article.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("id",articleId);
Article pendingArticle = new Article();
pendingArticle.setArticleStatus(pendingStatus);
int res = articleMapper.updateByExampleSelective(pendingArticle, example);
if (res != 1){
GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
}
service-article com/imooc/article/service/ArticleService.java
package com.imooc.article.service;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
import java.util.List;
public interface ArticleService {
/**
* 发布文章
*/
public void createArticle(NewArticleBO newArticleBO, Category category);
/**
* 更新定时发布为即使发布
*/
public void updateAppointToPublish();
/**
* 用户中心-查询我的文章列表
*/
public PagedGridResult queryMyArticleList(String userId, String keyword, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize);
/**
* 更改文章的状态
* @param articleId
* @param pendingStatus
*/
public void updateArticleStatus(String articleId, Integer pendingStatus);
}
admin文章管理列表【内容审核】【作业】
管理员查询用户的所有文章列表
service-api com/imooc/api/controller/article/ArticleControllerApi.java
@PostMapping("queryAllList")
@ApiOperation(value = "管理员查询用户的所有文章列表", notes = "管理员查询用户的所有文章列表", httpMethod = "POST")
public GraceJSONResult queryAllList(@RequestParam Integer status,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize);
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult queryAllList(Integer status, Integer page, Integer pageSize) {
if (page == null){
page = COMMON_START_PAGE;
}
if (pageSize == null){
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult = articleService.queryAllArticleListAdmin(status,page,pageSize);
return GraceJSONResult.ok(gridResult);
}
service-article com/imooc/article/service/ArticleService.java
/**
* 管理员查询文章列表
*/
public PagedGridResult queryAllArticleListAdmin(Integer status, Integer page, Integer pageSize);
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Override
public PagedGridResult queryAllArticleListAdmin(Integer status, Integer page, Integer pageSize) {
Example articleExample = new Example(Article.class);
articleExample.orderBy("createTime").desc();
Example.Criteria criteria = articleExample.createCriteria();
//这里是检测文章状态 与前端做匹配
if (ArticleReviewStatus.isArticleStatusValid(status)) {
criteria.andEqualTo("articleStatus", status);
}
// 审核中是机审和人审核的两个状态,所以需要单独判断
if (status != null && status == 12) {
criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
.orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
}
//isDelete必须是0
criteria.andEqualTo("isDelete", YesOrNo.NO.type);
/**
* page: 第几页
* pageSize: 每页显示条数
*/
PageHelper.startPage(page, pageSize);
List<Article> list = articleMapper.selectByExample(articleExample);
return setterPagedGrid(list, page);
}
人工审核 【内容审核】
内容审核 | 运营管理平台 (imoocnews.com) 【[待审核]手动审核通过】
service-api com/imooc/api/controller/article/ArticleControllerApi.java
@PostMapping("doReview")
@ApiOperation(value = "管理员对文章进行审核通过或者失败", notes = "管理员对文章进行审核通过或者失败", httpMethod = "POST")
public GraceJSONResult doReview(@RequestParam String articleId,
@RequestParam Integer passOrNot);
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult doReview(String articleId, Integer passOrNot) {
Integer pendingStatus;
if (passOrNot == YesOrNo.YES.type) {
// 审核成功
pendingStatus = ArticleReviewStatus.SUCCESS.type;
} else if (passOrNot == YesOrNo.NO.type) {
// 审核失败
pendingStatus = ArticleReviewStatus.FAILED.type;
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
// 保存到数据库,更改文章状态为审核成功或者失败
articleService.updateArticleStatus(articleId, pendingStatus);
return GraceJSONResult.ok();
}
撤回_删除文章作业 【内容管理】
媒体号作家中心 | 内容管理 (imoocnews.com)
[这个是用户撤回和删除噢 而不是管理员的撤回与删除]
用户:媒体号作家中心 | 内容管理 (imoocnews.com)
管理员:内容审核 | 运营管理平台 (imoocnews.com)
service-api com/imooc/api/controller/article/ArticleControllerApi.java
@PostMapping("/delete")
@ApiOperation(value = "用户删除文章", notes = "用户删除文章", httpMethod = "POST")
public GraceJSONResult delete(@RequestParam String userId,
@RequestParam String articleId);
@PostMapping("/withdraw")
@ApiOperation(value = "用户撤回文章", notes = "用户撤回文章", httpMethod = "POST")
public GraceJSONResult withdraw(@RequestParam String userId,
@RequestParam String articleId);
}
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult delete(String userId, String articleId) {
articleService.deleteArticle(userId,articleId);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult withdraw(String userId, String articleId) {
articleService.withdrawArticle(userId, articleId);
return GraceJSONResult.ok();
}
service-article com/imooc/article/service/ArticleService.java
package com.imooc.article.service;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
import java.util.List;
public interface ArticleService {
/**
* 删除文章
*/
public void deleteArticle(String userId, String articleId);
/**
* 撤回文章
*/
public void withdrawArticle(String userId, String articleId);
}
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Transactional
@Override
public void deleteArticle(String userId, String articleId) {
Example articleExample = makeExampleCriteria(userId, articleId);
Article pending = new Article();
pending.setIsDelete(YesOrNo.YES.type);
int result = articleMapper.updateByExampleSelective(pending, articleExample);
if (result != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_DELETE_ERROR);
}
}
@Transactional
@Override
public void withdrawArticle(String userId, String articleId) {
Example articleExample = makeExampleCriteria(userId, articleId);
Article pending = new Article();
pending.setArticleStatus(ArticleReviewStatus.WITHDRAW.type);
int result = articleMapper.updateByExampleSelective(pending, articleExample);
if (result != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_WITHDRAW_ERROR);
}
// deleteHTML(articleId);
}
private Example makeExampleCriteria(String userId, String articleId) {
Example articleExample = new Example(Article.class);
Example.Criteria criteria = articleExample.createCriteria();
criteria.andEqualTo("publishUserId", userId);
criteria.andEqualTo("id", articleId);
return articleExample;
}
首页_作者页面介绍【章节描述】
- 开发首页与作家个人展示页
- 文章列表、友情链接查询
- 粉丝关注与取关
- 我的粉丝与粉丝画像
根据MongoDB字段查询友情链接
service-api com/imooc/api/controller/admin/FriendLinkControllerApi.java
@ApiOperation(value = "门户端查询友情链接列表", notes = "门户端查询友情链接列表", httpMethod = "GET")
@GetMapping("portal/list")
public GraceJSONResult queryPortalAllFriendLinkList();
service-admin com/imooc/admin/controller/FriendLinkController.java
@Override
public GraceJSONResult queryPortalAllFriendLinkList() {
List<FriendLinkMO> list = friendLinkService.queryPortalAllFriendLinkList();
return GraceJSONResult.ok(list);
}
service-admin com/imooc/admin/service/FriendLinkService.java
/**
* 首页查询友情链接
*/
public List<FriendLinkMO> queryPortalAllFriendLinkList();
service-admin com/imooc/admin/service/impl/FriendLinkServiceImpl.java
@Override
public List<FriendLinkMO> queryPortalAllFriendLinkList() {
return friendLinkRepository.getAllByIsDelete(YesOrNo.NO.type);
}
service-admin com/imooc/admin/repository/FriendLinkRepository.java
@Repository
public interface FriendLinkRepository extends MongoRepository<FriendLinkMO, String> { //持久层
// 内置提供了很多方法 find.. delete...
public List<FriendLinkMO> getAllByIsDelete(Integer isDelete); //后面可以加ANDID
}
搜索并展示文章列表【首页】
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@Api(value = "门户端文章业务的controller", tags = {"门户端文章业务的controller"})
@RequestMapping("portal/article")
public interface ArticlePortalControllerApi {
@ApiOperation(value = "首页查询文章列表", notes = "首页查询文章列表", httpMethod = "GET")
@GetMapping("list")
public GraceJSONResult list(@RequestParam String keyword,
@RequestParam Integer category,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize);
}
service-article com/imooc/article/controller/ArticlePortalController.java
@RestController
public class ArticlePortalController extends BaseController implements ArticlePortalControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticlePortalController.class);
@Autowired
private ArticlePortalService articlePortalService;
@Autowired
private RestTemplate restTemplate;
@Override
public GraceJSONResult list(String keyword,
Integer category,
Integer page,
Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult
= articlePortalService.queryIndexArticleList(keyword,
category,
page,
pageSize);
return GraceJSONResult.ok(gridResult);
}
}
service-article com/imooc/article/service/impl/ArticlePortalServiceImpl.java
@Service
public class ArticlePortalServiceImpl extends BaseService implements ArticlePortalService {
@Autowired
private ArticleMapper articleMapper; //红色波浪线就去ArticleMapper上面加@Repository
@Override
public PagedGridResult queryIndexArticleList(String keyword,
Integer category,
Integer page,
Integer pageSize) {
Example articleExample = new Example(Article.class);
articleExample.orderBy("publishTime").desc();//使用时间进行排序
Example.Criteria criteria = articleExample.createCriteria();
/**
* 查询首页文章的自带隐性查询条件:
* isAppoint=即使发布,表示文章已经直接发布的,或者定时任务到点发布的
* isDelete=未删除,表示文章只能够显示未删除
* articleStatus=审核通过,表示只有文章经过机审/人工审核之后才能展示
*/
criteria.andEqualTo("isAppoint", YesOrNo.NO.type);
criteria.andEqualTo("isDelete", YesOrNo.NO.type);
criteria.andEqualTo("articleStatus", ArticleReviewStatus.SUCCESS.type);
if (StringUtils.isNotBlank(keyword)) {
criteria.andLike("title", "%" + keyword + "%");
}
if (category != null) {
criteria.andEqualTo("categoryId", category);
}
PageHelper.startPage(page, pageSize);
List<Article> list = articleMapper.selectByExample(articleExample);
System.out.println(keyword);
System.out.println(category);
return setterPagedGrid(list, page);
}
}
service-article com/imooc/article/service/ArticlePortalService.java
public interface ArticlePortalService {
/**
* 首页查询文章列表
*/
public PagedGridResult queryIndexArticleList(String keyword,
Integer category,
Integer page,
}
index.html
<!-- 中间容器 -->
<div class="container">
<!-- 文章列表 -->
<div id="articleList" class="article-list">
<ul>
<li class="single-article-wrapper" v-for="(article, index) in articleList" :key="index">
<img :src="article.articleCover" class="article-cover" v-show="article.articleType == 1">
<div class="single-article">
<div class="article-title">
<!-- TODO: 后期改为静态页面跳转 -->
<a :href="'detail.html?articleId='+article.id" target="_blank" class="link-article-title">{{article.title}}</a>
</div>
<div class="publisher">
<div class="category-tag" :style="{color: getCatTagColor(article.categoryId), borderColor: getCatTagColor(article.categoryId) }">{{getCatName(article.categoryId)}}</div>
<!-- TODO: 这里需要显示用户的昵称以及用户头像 -->
<img src="img/face1.png" class="publisher-face" v-if="article.publisherVO == null || article.publisherVO == undefined">
<div class="publisher-name" v-if="article.publisherVO == null || article.publisherVO == undefined"> {{article.publishUserId}} ⋅</div>
<img :src="article.publisherVO.face" class="publisher-face" v-if="article.publisherVO != null && article.publisherVO != undefined">
<!--
<a :href="'writer.html?writerId='+article.publisherVO.id" target="_blank">
<div class="publisher-name" v-if="article.publisherVO != null && article.publisherVO != undefined"> {{article.publisherVO.nickname}} ⋅</div>
</a>
-->
<div class="article-name"> {{article.readCounts}}阅读 ⋅</div>
<!-- <div class="publish-time"> {{formatData(article.publishTime)}}</div> -->
<div class="publish-time"> {{getDateBeforeNow(article.publishTime)}}</div>
</div>
</div>
</li>
</ul>
</div>
文章列表展示发布者需求【首页】
service-article com/imooc/article/controller/ArticlePortalController.java
[其他不变加上点代码]
package com.imooc.article.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.article.ArticleControllerApi;
import com.imooc.api.controller.article.ArticlePortalControllerApi;
import com.imooc.article.service.ArticlePortalService;
import com.imooc.article.service.ArticleService;
import com.imooc.enums.ArticleCoverType;
import com.imooc.enums.ArticleReviewStatus;
import com.imooc.enums.YesOrNo;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.Article;
import com.imooc.pojo.Category;
import com.imooc.pojo.bo.NewArticleBO;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@RestController
public class ArticlePortalController extends BaseController implements ArticlePortalControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticlePortalController.class);
@Autowired
private ArticlePortalService articlePortalService;
@Autowired
private RestTemplate restTemplate;
@Override
public GraceJSONResult list(String keyword,
Integer category,
Integer page,
Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult
= articlePortalService.queryIndexArticleList(keyword,
category,
page,
pageSize);
//START
List<Article> list = (List<Article>) gridResult.getRows();
// 1. 构建发布者id列表
Set<String> idset = new HashSet<>();
for (Article a : list){
// System.out.println(a.getPublishUserId());
idset.add(a.getPublishUserId());
}
System.out.println(idset.toString());
// 2. 发起远程调用(restTemplate),请求用户服务获得用户(idSet 发布者)列表
// 3. 拼接两个list,重组文章列表
//END
return GraceJSONResult.ok(gridResult);
}
}
发起restTemplate请求查询用户服务获得发布者列表【首页】二级用户
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult list(String keyword,
Integer category,
Integer page,
Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult
= articlePortalService.queryIndexArticleList(keyword,
category,
page,
pageSize);
//START 用户量大就双表关联查询 单表双查询 → 【首页不会显示发布者的用户id 和 头像】
List<Article> list = (List<Article>) gridResult.getRows();
// 1. 构建发布者id列表
Set<String> idset = new HashSet<>();
for (Article a : list){
// System.out.println(a.getPublishUserId());
idset.add(a.getPublishUserId());
}
System.out.println(idset.toString());
// 2. 发起远程调用(restTemplate),请求用户服务获得用户(idSet 发布者)列表
String userServerUrlExecute
= "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idset);
ResponseEntity<GraceJSONResult> responseEntity =
restTemplate.getForEntity(userServerUrlExecute,GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200){
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
for (AppUserVO u : publisherList){
System.out.println(u.toString());
}
// 3. 拼接两个list,重组文章列表
//END
return GraceJSONResult.ok(gridResult);
}
===================成功输出二级用户基本信息===============================
AppUserVO{id='240629F21AK1BHX4', nickname='15027597319', face='https://iimooc-news-dev.oss-cn-shanghai.aliyuncs.com/images/abc/240629F21AK1BHX4/240712FM0G1WMZHH.png', activeStatus=1}
AppUserVO{id='200628AFYM7AGWPH', nickname='我是慕课网', face='https://imooc-news-dev.oss-cn-shanghai.aliyuncs.com/images/abc/200628AFYM7AGWPH/2007088XH2WT7GXP.png', activeStatus=1}
dev-model com/imooc/pojo/vo/AppUserVO.java
public class AppUserVO {
private String id;
private String nickname;
private String face;
private Integer activeStatus;
}Getter + Setter + ToString
service-user com/imooc/user/controller/UserController.java
@RestController
public class UserController extends BaseController implements UserControllerApi {
@Override
public GraceJSONResult queryByIds(String userIds) {
if (StringUtils.isBlank(userIds)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
for (String userId : userIdList){
//获得用户基本信息
AppUserVO userVO = getBasicUserInfo(userId);
// 3.添加到publisherList
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
private AppUserVO getBasicUserInfo(String userId){
// 1. 根据userId查询用户的信息 UserService+impl
AppUser user = getUser(userId);
// 2. 返回用户信息
AppUserVO userVO = new AppUserVO();
BeanUtils.copyProperties(user, userVO); //拷贝信息
return userVO;
}
}
service-api com/imooc/api/controller/user/UserControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user")
public interface UserControllerApi {
@ApiOperation(value = "获得用户基本信息",notes = "获得用户基本信息",httpMethod = "POST")
@PostMapping("/getUserInfo")
public GraceJSONResult getUserInfo(@RequestParam String userId);
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
BindingResult result);
@ApiOperation(value = "根据用户的ids查询用户列表",notes = "根据用户的ids查询用户列表",httpMethod = "GET")
@GetMapping("/queryByids")
public GraceJSONResult queryByIds(@RequestParam String userIds);
}
重组文章列表并且渲染【首页】
index.html
<!-- 中间容器 -->
<div class="container">
<!-- 文章列表 -->
<div id="articleList" class="article-list">
<ul>
<li class="single-article-wrapper" v-for="(article, index) in articleList" :key="index">
<img :src="article.articleCover" class="article-cover" v-show="article.articleType == 1">
<div class="single-article">
<div class="article-title">
<!-- TODO: 后期改为静态页面跳转 -->
<a :href="'detail.html?articleId='+article.id" target="_blank" class="link-article-title">{{article.title}}</a>
</div>
<div class="publisher">
<div class="category-tag" :style="{color: getCatTagColor(article.categoryId), borderColor: getCatTagColor(article.categoryId) }">{{getCatName(article.categoryId)}}</div>
<!-- ★★★★★ TODO: 这里需要显示用户的昵称以及用户头像 ★★★★★ -->
<img src="img/face1.png" class="publisher-face" v-if="article.publisherVO == null || article.publisherVO == undefined">
<div class="publisher-name" v-if="article.publisherVO == null || article.publisherVO == undefined"> {{article.publishUserId}} ⋅</div>
<img :src="article.publisherVO.face" class="publisher-face" v-if="article.publisherVO != null && article.publisherVO != undefined">
<a :href="'writer.html?writerId='+article.publisherVO.id" target="_blank">
<div class="publisher-name" v-if="article.publisherVO != null && article.publisherVO != undefined"> {{article.publisherVO.nickname}} ⋅</div>
</a>
<div class="article-name"> {{article.readCounts}}阅读 ⋅</div>
<!-- <div class="publish-time"> {{formatData(article.publishTime)}}</div> -->
<div class="publish-time"> {{getDateBeforeNow(article.publishTime)}}</div>
</div>
</div>
</li>
</ul>
</div>
dev-model com/imooc/pojo/vo/IndexArticleVO.java
public class IndexArticleVO {
private String id;
private String title;
private Integer categoryId;
private Integer articleType;
private String articleCover;
private Integer isAppoint;
private Integer articleStatus;
private String publishUserId;
private Date publishTime;
private Integer readCounts;
private Integer commentCounts;
private String mongoFileId;
private Integer isDelete;
private Date createTime;
private Date updateTime;
private String content;
}Getter+Setter
service-article com/imooc/article/controller/ArticlePortalController.java
package com.imooc.article.controller;
......
import java.util.*;
@RestController
public class ArticlePortalController extends BaseController implements ArticlePortalControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticlePortalController.class);
@Autowired
private ArticlePortalService articlePortalService;
@Autowired
private RestTemplate restTemplate;
@Override
public GraceJSONResult list(String keyword,
Integer category,
Integer page,
Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult
= articlePortalService.queryIndexArticleList(keyword,
category,
page,
pageSize);
//START 用户量大就双表关联查询 单表双查询 → 【首页不会显示发布者的用户id 和 头像】
List<Article> list = (List<Article>) gridResult.getRows();
// 1. 构建发布者id列表
Set<String> idset = new HashSet<>();
for (Article a : list){
// System.out.println(a.getPublishUserId());
idset.add(a.getPublishUserId());
}
System.out.println(idset.toString());
// 2. 发起远程调用(restTemplate),请求用户服务获得用户(idSet 发布者)列表
String userServerUrlExecute
= "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idset);
ResponseEntity<GraceJSONResult> responseEntity =
restTemplate.getForEntity(userServerUrlExecute,GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200){
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
// for (AppUserVO u : publisherList){
// System.out.println(u.toString());
// }
// 3. 拼接两个list,重组文章列表
List<IndexArticleVO> indexArticleList = new ArrayList<>();
for (Article a : list){
IndexArticleVO indexArticleVO = new IndexArticleVO();
BeanUtils.copyProperties(a, indexArticleVO);
// 3.1 从publisherList中获得发布者的基本信息
AppUserVO publisher = getUserIfPublisher(a.getPublishUserId(), publisherList);
indexArticleVO.setPublisherVO(publisher);
indexArticleList.add(indexArticleVO);
}
gridResult.setRows(indexArticleList);
//END
return GraceJSONResult.ok(gridResult);
}
// 用于获得publish
private AppUserVO getUserIfPublisher(String publisherId, List<AppUserVO> publisherList){
for (AppUserVO user : publisherList){
if (user.getId().equalsIgnoreCase(publisherId)){
return user;
}
}
return null;
}
}
查询热闻【首页】阅读数从最新新闻进行排名
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@Api(value = "门户端文章业务的controller", tags = {"门户端文章业务的controller"})
@RequestMapping("portal/article")
public interface ArticlePortalControllerApi {
@GetMapping("hotList")
@ApiOperation(value = "首页查询新闻列表", notes = "首页查询新闻列表", httpMethod = "GET")
public GraceJSONResult hotList();
}
service-article com/imooc/article/service/ArticlePortalService.java
public interface ArticlePortalService {
/**
* 首页查询文章列表
*/
public PagedGridResult queryIndexArticleList(String keyword,
Integer category,
Integer page,
Integer pageSize);
/**
* 首页查询热闻列表
*/
public List<Article> queryHotList();
}
service-article com/imooc/article/service/impl/ArticlePortalServiceImpl.java
@Override
public List<Article> queryHotList() {
Example articleExample = new Example(Article.class);
Example.Criteria criteria = setDefualArticleExample(articleExample);
PageHelper.startPage(1, 5);
List<Article> list = articleMapper.selectByExample(articleExample);
return list;
}
private Example.Criteria setDefualArticleExample(Example articleExample) {
articleExample.orderBy("publishTime").desc();
Example.Criteria criteria = articleExample.createCriteria();
/**
* 查询首页文章的自带隐性查询条件:
* isAppoint=即使发布,表示文章已经直接发布的,或者定时任务到点发布的
* isDelete=未删除,表示文章只能够显示未删除
* articleStatus=审核通过,表示只有文章经过机审/人工审核之后才能展示
*/
criteria.andEqualTo("isAppoint", YesOrNo.NO.type);
criteria.andEqualTo("isDelete", YesOrNo.NO.type);
criteria.andEqualTo("articleStatus", ArticleReviewStatus.SUCCESS.type);
return criteria;
}
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult hotList() {
return GraceJSONResult.ok(articlePortalService.queryHotList());
}
基本信息展示_历史文章列表【作者主页】
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
package com.imooc.api.controller.article;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewArticleBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Date;
@Api(value = "门户端文章业务的controller", tags = {"门户端文章业务的controller"})
@RequestMapping("portal/article")
public interface ArticlePortalControllerApi {
@GetMapping("list")
@ApiOperation(value = "首页查询文章列表", notes = "首页查询文章列表", httpMethod = "GET")
public GraceJSONResult list(@RequestParam String keyword,
@RequestParam Integer category,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize);
@GetMapping("hotList")
@ApiOperation(value = "首页查询新闻列表", notes = "首页查询新闻列表", httpMethod = "GET")
public GraceJSONResult hotList();
/**
* 查询作家发布的所有文章列表
*/
@GetMapping("queryArticleListOfWriter")
@ApiOperation(value = "查询作家发布的所有文章列表", notes = "查询作家发布的所有文章列表", httpMethod = "GET")
public GraceJSONResult queryArticleListOfWriter(@RequestParam String writerId,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize);
@GetMapping("queryGoodArticleListOfWriter")
@ApiOperation(value = "作家页面查询近期佳文", notes = "作家页面查询近期佳文", httpMethod = "GET")
public GraceJSONResult queryGoodArticleListOfWriter(@RequestParam String writerId);
}
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult queryArticleListOfWriter(String writerId, Integer page, Integer pageSize) {
System.out.println("writerId=" + writerId);
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult = articlePortalService.queryArticleListOfWriter(writerId, page, pageSize);
gridResult = rebuildArticleGrid(gridResult);
return GraceJSONResult.ok(gridResult);
}
@Override
public GraceJSONResult queryGoodArticleListOfWriter(String writerId) {
PagedGridResult gridResult = articlePortalService.queryGoodArticleListOfWriter(writerId);
return GraceJSONResult.ok(gridResult);
}
}
/* 完全版ArticlePortalController
package com.imooc.article.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.article.ArticlePortalControllerApi;
import com.imooc.article.service.ArticlePortalService;
import com.imooc.article.service.ArticleService;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.Article;
import com.imooc.pojo.vo.AppUserVO;
import com.imooc.pojo.vo.IndexArticleVO;
import com.imooc.utils.IPUtil;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RestController
public class ArticlePortalController extends BaseController implements ArticlePortalControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticlePortalController.class);
@Autowired
private ArticlePortalService articlePortalService;
@Autowired
private RestTemplate restTemplate;
@Override
public GraceJSONResult list(String keyword,
Integer category,
Integer page,
Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult
= articlePortalService.queryIndexArticleList(keyword,
category,
page,
pageSize);
gridResult = rebuildArticleGrid(gridResult);
return GraceJSONResult.ok(gridResult);
}
private PagedGridResult rebuildArticleGrid(PagedGridResult gridResult) {
// START
List<Article> list = (List<Article>)gridResult.getRows();
// 1. 构建发布者id列表
Set<String> idSet = new HashSet<>();
List<String> idList = new ArrayList<>();
for (Article a : list) {
// System.out.println(a.getPublishUserId());
// 1.1 构建发布者的set
idSet.add(a.getPublishUserId());
// 1.2 构建文章id的list
idList.add(REDIS_ARTICLE_READ_COUNTS + ":" + a.getId());
}
System.out.println(idSet.toString());
// 发起redis的mget批量查询api,获得对应的值
List<String> readCountsRedisList = redis.mget(idList);
List<AppUserVO> publisherList = getPublisherList(idSet);
// 3. 拼接两个list,重组文章列表
List<IndexArticleVO> indexArticleList = new ArrayList<>();
for (int i = 0 ; i < list.size() ; i ++) {
IndexArticleVO indexArticleVO = new IndexArticleVO();
Article a = list.get(i);
BeanUtils.copyProperties(a, indexArticleVO);
// 3.1 从publisherList中获得发布者的基本信息
AppUserVO publisher = getUserIfPublisher(a.getPublishUserId(), publisherList);
indexArticleVO.setPublisherVO(publisher);
// 3.2 重新组装设置文章列表中的阅读量
String redisCountsStr = readCountsRedisList.get(i);
int readCounts = 0;
if (StringUtils.isNotBlank(redisCountsStr)) {
readCounts = Integer.valueOf(redisCountsStr);
}
indexArticleVO.setReadCounts(readCounts);
indexArticleList.add(indexArticleVO);
}
gridResult.setRows(indexArticleList);
// END
return gridResult;
}
private AppUserVO getUserIfPublisher(String publisherId,
List<AppUserVO> publisherList) {
for (AppUserVO user : publisherList) {
if (user.getId().equalsIgnoreCase(publisherId)) {
return user;
}
}
return null;
}
// 发起远程调用,获得用户的基本信息
private List<AppUserVO> getPublisherList(Set idSet) {
String userServerUrlExecute
= "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return publisherList;
}
@Override
public GraceJSONResult hotList() {
return GraceJSONResult.ok(articlePortalService.queryHotList());
}
@Override
public GraceJSONResult queryArticleListOfWriter(String writerId, Integer page, Integer pageSize) {
System.out.println("writerId=" + writerId);
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult = articlePortalService.queryArticleListOfWriter(writerId, page, pageSize);
gridResult = rebuildArticleGrid(gridResult);
return GraceJSONResult.ok(gridResult);
}
@Override
public GraceJSONResult queryGoodArticleListOfWriter(String writerId) {
PagedGridResult gridResult = articlePortalService.queryGoodArticleListOfWriter(writerId);
return GraceJSONResult.ok(gridResult);
}
}
*/
service-article com/imooc/article/service/impl/ArticlePortalServiceImpl.java
public interface ArticlePortalService {
/**
* 首页查询文章列表
*/
public PagedGridResult queryIndexArticleList(String keyword,
Integer category,
Integer page,
Integer pageSize);
/**
* 首页查询热闻列表
*/
public List<Article> queryHotList();
/**
* 查询作家发布的所有文章列表
*/
public PagedGridResult queryArticleListOfWriter(String writerId,
Integer page,
Integer pageSize);
/**
* 作家页面查询近期佳文
*/
public PagedGridResult queryGoodArticleListOfWriter(String writerId);
service-article com/imooc/article/service/impl/ArticlePortalServiceImpl.java
@Override
public PagedGridResult queryArticleListOfWriter(String writerId, Integer page, Integer pageSize) {
Example articleExample = new Example(Article.class);
Example.Criteria criteria = setDefualArticleExample(articleExample);
criteria.andEqualTo("publishUserId", writerId);
/**
* page: 第几页
* pageSize: 每页显示条数
*/
PageHelper.startPage(page, pageSize);
List<Article> list = articleMapper.selectByExample(articleExample);
return setterPagedGrid(list, page);
}
@Override
public PagedGridResult queryGoodArticleListOfWriter(String writerId) {
Example articleExample = new Example(Article.class);
articleExample.orderBy("publishTime").desc();
Example.Criteria criteria = setDefualArticleExample(articleExample);
criteria.andEqualTo("publishUserId", writerId);
/**
* page: 第几页
* pageSize: 每页显示条数
*/
PageHelper.startPage(1, 5);
List<Article> list = articleMapper.selectByExample(articleExample);
return setterPagedGrid(list, 1);
}
private Example.Criteria setDefualArticleExample(Example articleExample) {
articleExample.orderBy("publishTime").desc();
Example.Criteria criteria = articleExample.createCriteria();
/**
* 查询首页文章的自带隐性查询条件:
* isAppoint=即使发布,表示文章已经直接发布的,或者定时任务到点发布的
* isDelete=未删除,表示文章只能够显示未删除
* articleStatus=审核通过,表示只有文章经过机审/人工审核之后才能展示
*/
criteria.andEqualTo("isAppoint", YesOrNo.NO.type);
criteria.andEqualTo("isDelete", YesOrNo.NO.type);
criteria.andEqualTo("articleStatus", ArticleReviewStatus.SUCCESS.type);
return criteria;
}
关注与取关_redis单线程计数统计 【粉丝关注】
阅读数可以用数据库COUNT* 但是压力会很大 若很多人一起刷新会音响很大
用redis 数量累加累减 单线程安全
减少数据库压力
【注意 redis我安装到了本地计算机里面 D:\Redis-x64-3.0.504】
打开redis-cli.exe
127.0.0.1:6379> keys *
1) "redis_all_category"
2) "redis_admin_token:1001"
3) "redis_user_info:1001"
4) "redis_user_info:200628AFYM7AGWPH"
5) "redis_user_token:240629F21AK1BHX4"
6) "redis_user_info:240629F21AK1BHX4"
7) "redis_user_token:200628AFYM7AGWPH"
127.0.0.1:6379> INCR 1001:fans #【增加】
(integer) 1
127.0.0.1:6379> INCR 1001:fans
(integer) 2
127.0.0.1:6379> INCR 1001:fans
(integer) 3
127.0.0.1:6379> get 1001:fans #【获取】
"3"
127.0.0.1:6379> DECR 1001:fans #【减少】
(integer) 2
127.0.0.1:6379> DECR 1001:fans
(integer) 1
127.0.0.1:6379> incr 1001:follows #【关注的粉丝】
(integer) 1
127.0.0.1:6379> get 1001:follows
"1"
查询用户关注状态【粉丝关注】
service-api com/imooc/api/controller/user/MyFansControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
@Api(value = "粉丝管理",tags = {"粉丝管理功能的controller"})
@RequestMapping("fans")
public interface MyFansControllerApi {
@ApiOperation(value = "查询当前用户是否关注作家",notes = "查询当前用户是否关注作家",httpMethod = "POST")
@PostMapping("/isMeFollowThisWriter")
public GraceJSONResult isMeFollowThisWriter(@RequestParam String writerId, @RequestParam String fanId);
}
service-user com/imooc/user/controller/MyFansController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.api.controller.user.MyFansControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.user.service.MyFansService;
import com.imooc.utils.RedisOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyFansController extends BaseController implements MyFansControllerApi {
final static Logger logger = LoggerFactory.getLogger(MyFansController.class);
@Autowired
private MyFansService myFansService;
@Override
public GraceJSONResult isMeFollowThisWriter(String writerId, String fanId) {
boolean res = myFansService.isMeFollowThisWriter(writerId,fanId);
return GraceJSONResult.ok(res);
}
}
service-user com/imooc/user/service/MyFansService.java
package com.imooc.user.service;
import com.imooc.utils.PagedGridResult;
import java.util.Date;
public interface MyFansService {
/**
* 查询当前用户是否关注作家
*/
public boolean isMeFollowThisWriter(String writerId, String fanId);
}
service-user com/imooc/user/service/impl/MyFansServiceImpl.java
package com.imooc.user.service.impl;
import com.github.pagehelper.PageHelper;
import com.imooc.api.service.BaseService;
import com.imooc.enums.UserStatus;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.Fans;
import com.imooc.user.mapper.AppUserMapper;
import com.imooc.user.mapper.FansMapper;
import com.imooc.user.service.AppUserMngService;
import com.imooc.user.service.MyFansService;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
public class MyFansServiceImpl extends BaseService implements MyFansService {
@Autowired
public FansMapper fansMapper;
@Override
public boolean isMeFollowThisWriter(String writerId, String fanId) {
Fans fan = new Fans();
fan.setFanId(fanId);
fan.setWriterId(writerId);
int count = fansMapper.selectCount(fan); //前期先放在数据库里
return count > 0 ? true : false;
}
}
service-user com/imooc/user/mapper/FansMapper.java
package com.imooc.user.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Fans;
import org.springframework.stereotype.Repository;
@Repository
public interface FansMapper extends MyMapper<Fans> {
}
用户关注_粉丝累加 && 粉丝累减
service-api com/imooc/api/controller/user/MyFansControllerApi.java
package com.imooc.api.controller.user;
import com.imooc.grace.result.GraceJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
@Api(value = "粉丝管理",tags = {"粉丝管理功能的controller"})
@RequestMapping("fans")
public interface MyFansControllerApi {
@ApiOperation(value = "用户关注作家,成为粉丝",notes = "用户关注作家,成为粉丝",httpMethod = "POST")
@PostMapping("/follow")
public GraceJSONResult follow(@RequestParam String writerId, @RequestParam String fanId);
@ApiOperation(value = "取消关注,作家损失粉丝",notes = "取消关注,作家损失粉丝",httpMethod = "POST")
@PostMapping("/unfollow")
public GraceJSONResult unfollow(@RequestParam String writerId, @RequestParam String fanId);
}
service-user com/imooc/user/controller/MyFansController.java
package com.imooc.user.controller;
import com.imooc.api.BaseController;
import com.imooc.api.controller.user.MyFansControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.user.service.MyFansService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyFansController extends BaseController implements MyFansControllerApi {
final static Logger logger = LoggerFactory.getLogger(MyFansController.class);
@Autowired
private MyFansService myFansService;
@Override
public GraceJSONResult isMeFollowThisWriter(String writerId, String fanId) {
boolean res = myFansService.isMeFollowThisWriter(writerId,fanId);
return GraceJSONResult.ok(res);
}
@Override
public GraceJSONResult follow(String writerId, String fanId) {
myFansService.follow(writerId,fanId);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult unfollow(String writerId, String fanId) {
myFansService.unfollow(writerId, fanId);
return GraceJSONResult.ok();
}
}
service-user com/imooc/user/service/MyFansService.java
package com.imooc.user.service;
public interface MyFansService {
/**
* 查询当前用户是否关注作家
*/
public boolean isMeFollowThisWriter(String writerId, String fanId);
/**
* 关注成为粉丝
*/
public void follow(String writerId, String fanId);
/**
* 粉丝取消关注
*/
public void unfollow(String writerId, String fanId);
}
service-user com/imooc/user/service/impl/MyFansServiceImpl.java
package com.imooc.user.service.impl;
import com.github.pagehelper.PageHelper;
import com.imooc.api.service.BaseService;
import com.imooc.enums.UserStatus;
import com.imooc.pojo.AppUser;
import com.imooc.pojo.Fans;
import com.imooc.user.mapper.AppUserMapper;
import com.imooc.user.mapper.FansMapper;
import com.imooc.user.service.AppUserMngService;
import com.imooc.user.service.MyFansService;
import com.imooc.utils.PagedGridResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
import java.util.Date;
import java.util.List;
@Service
public class MyFansServiceImpl extends BaseService implements MyFansService {
@Autowired
public FansMapper fansMapper;
@Override
public boolean isMeFollowThisWriter(String writerId, String fanId) {
Fans fan = new Fans();
fan.setFanId(fanId);
fan.setWriterId(writerId);
int count = fansMapper.selectCount(fan); //前期先放在数据库里
return count > 0 ? true : false;
}
@Transactional
@Override
public void follow(String writerId, String fanId) {
// 获得粉丝用户的信息
AppUser fanInfo = userService.getUser(fanId);
String fanPkId = sid.nextShort();
Fans fans = new Fans();
fans.setId(fanPkId);
fans.setFanId(fanId);
fans.setWriterId(writerId);
fans.setFace(fanInfo.getFace());
fans.setFanNickname(fanInfo.getNickname());
fans.setSex(fanInfo.getSex());
fans.setProvince(fanInfo.getProvince());
fansMapper.insert(fans);
// redis 作家粉丝数累加
redis.increment(REDIS_WRITER_FANS_COUNTS + ":" + writerId, 1); //增加key一次
// redis 当前用户的(我的)关注数累加
redis.increment(REDIS_MY_FOLLOW_COUNTS + ":" + fanId, 1); //增加key一次
}
@Transactional
@Override
public void unfollow(String writerId, String fanId) {
Fans fans = new Fans();
fans.setWriterId(writerId);
fans.setFanId(fanId);
fansMapper.delete(fans);
// redis 作家粉丝数累减
redis.decrement(REDIS_WRITER_FANS_COUNTS + ":" + writerId, 1); //增加key一次
// redis 当前用户的(我的)关注数累减
redis.decrement(REDIS_MY_FOLLOW_COUNTS + ":" + fanId, 1); //增加key一次
}
}
service-api com/imooc/api/service/BaseService.java
package com.imooc.api.service;
import com.github.pagehelper.PageInfo;
import com.imooc.utils.PagedGridResult;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class BaseService {
public static final String REDIS_ALL_CATEGORY = "redis_all_category";
public static final String REDIS_WRITER_FANS_COUNTS = "redis_writer_fans_counts";
public static final String REDIS_MY_FOLLOW_COUNTS = "redis_my_follow_counts";
public static final String REDIS_ARTICLE_COMMENT_COUNTS = "redis_article_comment_counts";
@Autowired
public RedisOperator redis;
public PagedGridResult setterPagedGrid(List<?> list, Integer page){ //类型是? 后期不确定是什么泛型
PageInfo<?> pageList = new PageInfo<>(list);
PagedGridResult gridResult = new PagedGridResult();
gridResult.setRows(list);
gridResult.setPage(page);
gridResult.setRecords(pageList.getTotal());
gridResult.setTotal(pageList.getPages());
return gridResult;
}
}
service-api com/imooc/api/config/InterceptorConfig.java //增加粉丝接口的拦截
package com.imooc.api.config;
import com.imooc.api.interceptors.AdminTokenInterceptor;
import com.imooc.api.interceptors.PassportInterceptor;
import com.imooc.api.interceptors.UserActiveInterceptor;
import com.imooc.api.interceptors.UserTokenInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor passportInterceptor(){
return new PassportInterceptor();
}
@Bean
public UserTokenInterceptor userTokenInterceptor(){
return new UserTokenInterceptor();
}
@Bean
public UserActiveInterceptor userActiveInterceptor() {
return new UserActiveInterceptor();
}
@Bean
public AdminTokenInterceptor adminTokenInterceptor() {
return new AdminTokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
registry.addInterceptor(passportInterceptor())
.addPathPatterns("/passport/getSMSCode"); //拦截PassportControllerApi里的信息
registry.addInterceptor(userTokenInterceptor())
.addPathPatterns("/user/getAccountInfo")
.addPathPatterns("/user/updateUserId")
.addPathPatterns("/fs/uploadFace")
.addPathPatterns("/fs/uploadSomeFiles")
.addPathPatterns("/fans/follow")
.addPathPatterns("/fans/unfollow");
registry.addInterceptor(adminTokenInterceptor())//继续添加拦截器:查询admin列表 创建新admin用户
.addPathPatterns("/adminMng/adminIsExist")
.addPathPatterns("/adminMng/addNewAdmin")
.addPathPatterns("/adminMng/getAdminList")
.addPathPatterns("/fs/uploadToGridFS")
.addPathPatterns("/friendLinkMng/saveOrUpdateFriendLink")
.addPathPatterns("/friendLinkMng/getFriendLinkList")
.addPathPatterns("/friendLinkMng/delete")
.addPathPatterns("/categoryMng/saveOrUpdateCategory")
.addPathPatterns("/categoryMng/getCatList");
registry.addInterceptor(userActiveInterceptor())
.addPathPatterns("/fs/uploadSomeFiles")
.addPathPatterns("/fans/follow")
.addPathPatterns("/fans/unfollow");
}
}
粉丝数与关注数页面显示【粉丝关注】
service-user com/imooc/user/controller/UserController.java
@RestController
public class UserController extends BaseController implements UserControllerApi {
@Override
public GraceJSONResult getUserInfo(String userId) {
//接口进行解耦!!
// 0. 判断参数不为空
if (StringUtils.isBlank(userId)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.UN_LOGIN);
}
// 1. 根据userId查询用户的信息 UserService+impl
AppUser user = getUser(userId);
// 2. 返回用户信息
AppUserVO userVO = new AppUserVO();
BeanUtils.copyProperties(user, userVO); //拷贝信息
// 3. 查询redis中用户的关注数和粉丝数,放入userVO放入前端渲染
userVO.setMyFansCounts(getCountsFromRedis(REDIS_WRITER_FANS_COUNTS + ":" + userId));
userVO.setMyFollowCounts(getCountsFromRedis(REDIS_MY_FOLLOW_COUNTS + ":" + userId));
return GraceJSONResult.ok(userVO);
}
}
service-api com/imooc/api/BaseController.java
public abstract class BaseController {
public Integer getCountsFromRedis(String key){
String countsStr = redis.get(key);
if (StringUtils.isBlank(countsStr)) {
countsStr = "0";
}
return Integer.valueOf(countsStr);
}
}
dev-model com/imooc/pojo/vo/AppUserVO.java
public class AppUserVO {
private String id;
private String nickname;
private String face;
private Integer activeStatus;
private Integer myFollowCounts;
private Integer myFansCounts;
}Getter + Setter
我的粉丝列表_后端分页查询【粉丝管理】
service-api com/imooc/api/controller/user/MyFansControllerApi.java
@Api(value = "粉丝管理",tags = {"粉丝管理功能的controller"})
@RequestMapping("fans")
public interface MyFansControllerApi {
@ApiOperation(value = "查询我的所有粉丝列表", notes = "查询我的所有粉丝列表", httpMethod = "POST")
@PostMapping("/queryAll")
public GraceJSONResult queryAll(
@RequestParam String writerId,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页查询每一页显示的条数", required = false)
@RequestParam Integer pageSize);
}
service-user com/imooc/user/controller/MyFansController.java
@RestController
public class MyFansController extends BaseController implements MyFansControllerApi {
@Override
public GraceJSONResult queryAll(String writerId, Integer page, Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
return GraceJSONResult.ok(myFansService.queryMyFansList(writerId, page, pageSize));
}
}
===================================================================
http://writer.imoocnews.com:9090/imooc-news/writer/myFans.html
数据库中fans表
weiter_id
可以改成登录的cookie里面的 uid:240629F21AK1BHX4
就可以测试用户粉丝数量
service-user com/imooc/user/service/MyFansService.java
package com.imooc.user.service;
import com.imooc.utils.PagedGridResult;
public interface MyFansService {
/**
* 查询当前用户是否关注作家
*/
public boolean isMeFollowThisWriter(String writerId, String fanId);
/**
* 关注成为粉丝
*/
public void follow(String writerId, String fanId);
/**
* 粉丝取消关注
*/
public void unfollow(String writerId, String fanId);
/**
* 查询我的粉丝
*/
public PagedGridResult queryMyFansList(String writerId, Integer page, Integer pageSize);
}
service-user com/imooc/user/service/impl/MyFansServiceImpl.java
@Service
public class MyFansServiceImpl extends BaseService implements MyFansService {
@Override
public PagedGridResult queryMyFansList(String writerId, Integer page, Integer pageSize) {
Fans fans = new Fans();
fans.setWriterId(writerId);
PageHelper.startPage(page,pageSize); //进行分页
List<Fans> list = fansMapper.select(fans);
return setterPagedGrid(list,page);
}
}
男女比例柱状图_饼状图显示【数据可视化-粉丝画像】Echarts
Apache ECharts + 前端 [myFansCharts-static.html + myFansCharts.html]
service-api com/imooc/api/controller/user/MyFansControllerApi.java
@Api(value = "粉丝管理",tags = {"粉丝管理功能的controller"})
@RequestMapping("fans")
public interface MyFansControllerApi {
@ApiOperation(value = "查询男女粉丝数量", notes = "查询男女粉丝数量", httpMethod = "POST")
@PostMapping("/queryRatio")
public GraceJSONResult queryRatio(@RequestParam String writerId);
}
service-user com/imooc/user/controller/MyFansController.java
@RestController
public class MyFansController extends BaseController implements MyFansControllerApi {
@Override
public GraceJSONResult queryRatio(String writerId) {
int manCount = myFansService.queryFansCounts(writerId, Sex.man);
int womanCount = myFansService.queryFansCounts(writerId, Sex.woman);
FansCountsVO fansCountsVO = new FansCountsVO();
fansCountsVO.setManCounts(manCount);
fansCountsVO.setWomanCounts(womanCount);
return GraceJSONResult.ok(fansCountsVO);
}
}
service-user com/imooc/user/service/MyFansService.java
package com.imooc.user.service;
import com.imooc.utils.PagedGridResult;
public interface MyFansService {
/**
* 查询粉丝数
*/
public Integer queryFansCounts(String writerId, Sex sex);
}
service-user com/imooc/user/service/impl/MyFansServiceImpl.java
@Service
public class MyFansServiceImpl extends BaseService implements MyFansService {
@Override
public Integer queryFansCounts(String writerId, Sex sex) {
Fans fans = new Fans();
fans.setWriterId(writerId);
fans.setSex(sex.type);
int count = fansMapper.selectCount(fans);
return count;
}
}
中国地图粉丝地域分布数量展示【数据可视化-粉丝画像】
男女比例柱状图_饼状图显示【数据可视化-粉丝画像】Echarts
Apache ECharts + 前端 [myFansCharts-static.html + myFansCharts.html]
service-api com/imooc/api/controller/user/MyFansControllerApi.java
@ApiOperation(value = "根据地域查询粉丝数量", notes = "根据地域查询粉丝数量", httpMethod = "POST")
@PostMapping("/queryRatioByRegion")
public GraceJSONResult queryRatioByRegion(@RequestParam String writerId);
}
service-user com/imooc/user/controller/MyFansController.java
@RestController
public class MyFansController extends BaseController implements MyFansControllerApi {
@Override
public GraceJSONResult queryRatioByRegion(String writerId) {
return GraceJSONResult.ok(myFansService.queryRegionRatioCounts(writerId));
}
}
=====================================================================
将fans里的writer_id【自己的cookie里的uid 属于自己的属性 对应着右面的province省份】
service-user com/imooc/user/service/MyFansService.java
package com.imooc.user.service;
import com.imooc.utils.PagedGridResult;
public interface MyFansService {
/**
* 查询粉丝数
*/
public List<RegionRatioVO> queryRegionRatioCounts(String writerId);
}
service-user com/imooc/user/service/impl/MyFansServiceImpl.java
@Service
public class MyFansServiceImpl extends BaseService implements MyFansService {
@Override
public List<RegionRatioVO> queryRegionRatioCounts(String writerId) {
Fans fans = new Fans();
fans.setWriterId(writerId);
List<RegionRatioVO> list = new ArrayList<>();
for (String r : regions) {
fans.setProvince(r);
Integer count = fansMapper.selectCount(fans);
RegionRatioVO regionRatioVO = new RegionRatioVO();
regionRatioVO.setName(r);
regionRatioVO.setValue(count);
list.add(regionRatioVO);
}
return list;
}
public static final String[] regions = {"北京", "天津", "上海", "重庆",
"河北", "山西", "辽宁", "吉林", "黑龙江", "江苏", "浙江", "安徽", "福建", "江西", "山东",
"河南", "湖北", "湖南", "广东", "海南", "四川", "贵州", "云南", "陕西", "甘肃", "青海", "台湾",
"内蒙古", "广西", "西藏", "宁夏", "新疆",
"香港", "澳门"};
}
dev-model com/imooc/pojo/vo/RegionRatioVO.java
package com.imooc.pojo.vo;
public class RegionRatioVO {
private String name;
private Integer value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
开发文章详情接口 【章节概述】detail.html
- 文章详情页
- 文章评论模块
- 评论管理
文章详情页
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@GetMapping("detail")
@ApiOperation(value = "文章详情查询", notes = "文章详情查询", httpMethod = "GET")
public GraceJSONResult detail(@RequestParam String articleId);
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult detail(String articleId) {
ArticleDetailVO detailVO = articlePortalService.queryDetail(articleId);
Set<String> idSet = new HashSet();
idSet.add(detailVO.getPublishUserId());
List<AppUserVO> publisherList = getPublisherList(idSet);
if (!publisherList.isEmpty()) {
detailVO.setPublishUserName(publisherList.get(0).getNickname());
}
detailVO.setReadCounts(
getCountsFromRedis(REDIS_ARTICLE_READ_COUNTS + ":" + articleId));
return GraceJSONResult.ok(detailVO);
}
==================================================================
http://www.imoocnews.com:9090/imooc-news/portal/detail.html?articleId=240721DDAHBPWG0H
service-article com/imooc/article/service/ArticlePortalService.java
/**
* 查询文章详情
*/
public ArticleDetailVO queryDetail(String articleId);
service-article com/imooc/article/service/impl/ArticlePortalServiceImpl.java
@Override
public ArticleDetailVO queryDetail(String articleId) {
Article article = new Article();
article.setId(articleId);
article.setIsAppoint(YesOrNo.NO.type);
article.setIsDelete(YesOrNo.NO.type);
article.setArticleStatus(ArticleReviewStatus.SUCCESS.type);
Article result = articleMapper.selectOne(article);
ArticleDetailVO detailVO = new ArticleDetailVO();
BeanUtils.copyProperties(result, detailVO);
return detailVO;
}
dev-model com/imooc/pojo/vo/ArticleDetailVO.java
package com.imooc.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class ArticleDetailVO {
private String id;
private String title;
private String cover;
private Integer categoryId;
private String categoryName;
private String publishUserId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishTime;
private String content;
private String publishUserName;
private Integer readCounts;
}Getter + Setter
阅读文章_阅读量redis累加【详情页】
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@PostMapping("readArticle")
@ApiOperation(value = "阅读文章,文章阅读量累加", notes = "阅读文章,文章阅读量累加", httpMethod = "POST")
public GraceJSONResult readArticle(@RequestParam String articleId);
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult detail(String articleId) {
ArticleDetailVO detailVO = articlePortalService.queryDetail(articleId);
Set<String> idSet = new HashSet();
idSet.add(detailVO.getPublishUserId());
List<AppUserVO> publisherList = getPublisherList(idSet);
if (!publisherList.isEmpty()) {
detailVO.setPublishUserName(publisherList.get(0).getNickname());
}
detailVO.setReadCounts( //去redis获取值 关联到前端阅读量增加 关联!!!
getCountsFromRedis(REDIS_ARTICLE_READ_COUNTS + ":" + articleId));
return GraceJSONResult.ok(detailVO);
}
@Override
public GraceJSONResult readArticle(String articleId) {
redis.increment(REDIS_ARTICLE_READ_COUNTS + ":" + articleId, 1);
return GraceJSONResult.ok();
}
==================================================================
http://www.imoocnews.com:9090/imooc-news/portal/detail.html?articleId=240721DDAHBPWG0H
service-article com/imooc/article/service/ArticlePortalService.java
public class ArticleDetailVO {
private String id;
private String title;
private String cover;
private Integer categoryId;
private String categoryName;
private String publishUserId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishTime;
private String content;
private String publishUserName;
private Integer readCounts;
}Getter + Setter
文章阅读数防刷策略【详情页】
限定id去做增加 readArticle中增加拦截器
在ArticlePortalControllerApi.java中的readArticle接口 增加 HttpServletRequest request
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@PostMapping("readArticle")
@ApiOperation(value = "阅读文章,文章阅读量累加", notes = "阅读文章,文章阅读量累加", httpMethod = "POST")
public GraceJSONResult readArticle(@RequestParam String articleId, HttpServletRequest request);
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public GraceJSONResult readArticle(String articleId, HttpServletRequest request) {
String userIp = IPUtil.getRequestIp(request);
// 设置针对当前用户ip的永久存在的key,存入redis,表示该ip的用户已经阅读过了 防刷策略
redis.setnx(REDIS_ALREADY_READ + ":" + articleId + ":" + userIp, userIp);
redis.increment(REDIS_ARTICLE_READ_COUNTS + ":" + articleId, 1);
return GraceJSONResult.ok();
}
service-api com/imooc/api/interceptors/ArticleReadInterceptor.java //【增加拦截器】
package com.imooc.api.interceptors;
import com.imooc.utils.IPUtil;
import com.imooc.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ArticleReadInterceptor extends BaseInterceptor implements HandlerInterceptor {
@Autowired
public RedisOperator redis;
public static final String REDIS_ALREADY_READ = "redis_already_read";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String articleId = request.getParameter("articleId");
String userIp = IPUtil.getRequestIp(request);
boolean isExist = redis.keyIsExist(REDIS_ALREADY_READ + ":" + articleId + ":" + userIp);
if (isExist) {
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
service-api com/imooc/api/config/InterceptorConfig.java
package com.imooc.api.config;
import com.imooc.api.interceptors.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
...
@Bean
public ArticleReadInterceptor articleReadInterceptor(){
return new ArticleReadInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){//注册拦截器
...
registry.addInterceptor(articleReadInterceptor())
.addPathPatterns("/portal/article/readArticle");
}
}
Redis mget 批量查询组装阅读量并展示【文章列表】
Redis get单个读取 && Redis mget批量读取
service-article com/imooc/article/controller/ArticlePortalController.java
private PagedGridResult rebuildArticleGrid(PagedGridResult gridResult) {
// START
List<Article> list = (List<Article>)gridResult.getRows();
// 1. 构建发布者id列表
Set<String> idSet = new HashSet<>();
List<String> idList = new ArrayList<>();
for (Article a : list) {
// System.out.println(a.getPublishUserId());
// 1.1 构建发布者的set
idSet.add(a.getPublishUserId());
// 1.2 构建文章id的list 包含所有key的值
idList.add(REDIS_ARTICLE_READ_COUNTS + ":" + a.getId());
}
System.out.println(idSet.toString());
// 发起redis的mget批量查询api,获得对应的值
List<String> readCountsRedisList = redis.mget(idList);
List<AppUserVO> publisherList = getPublisherList(idSet);
// 3. 拼接两个list,重组文章列表
List<IndexArticleVO> indexArticleList = new ArrayList<>();
for (int i = 0 ; i < list.size() ; i ++) {
IndexArticleVO indexArticleVO = new IndexArticleVO();
Article a = list.get(i); //属性值拷贝
BeanUtils.copyProperties(a, indexArticleVO);
// 3.1 从publisherList中获得发布者的基本信息
AppUserVO publisher = getUserIfPublisher(a.getPublishUserId(), publisherList);
indexArticleVO.setPublisherVO(publisher);
// 3.2 重新组装设置文章列表中的阅读量
String redisCountsStr = readCountsRedisList.get(i);
int readCounts = 0;
if (StringUtils.isNotBlank(redisCountsStr)) {
readCounts = Integer.valueOf(redisCountsStr);
}
indexArticleVO.setReadCounts(readCounts);
indexArticleList.add(indexArticleVO);
}
gridResult.setRows(indexArticleList);
// END
return gridResult;
}
用户发表评论【文章评论】
mybatis-generator-database generatorConfig-article.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 通用mapper所在目录 -->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="com.imooc.my.mapper.MyMapper"/>
</plugin>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/imooc-news-dev"
userId="root"
password="root">
</jdbcConnection>
<!-- 对应生成的pojo所在包 -->
<javaModelGenerator targetPackage="com.imooc.pojo" targetProject="mybatis-generator-database/src/main/java"/>
<!-- 对应生成的mapper所在目录 -->
<sqlMapGenerator targetPackage="mapper.article" targetProject="mybatis-generator-database/src/main/resources"/>
<!-- 配置mapper对应的java映射 -->
<javaClientGenerator targetPackage="com.imooc.article.mapper" targetProject="mybatis-generator-database/src/main/java" type="XMLMAPPER"/>
<!-- 数据库表 -->
<table tableName="comments"></table>
</context>
</generatorConfiguration>
mybatis-generator-database com/imooc/mybatis/utils/ArticleGenerator.java //【运行】
package com.imooc.mybatis.utils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ArticleGenerator {
public void generator() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("mybatis-generator-database"
+ File.separator
+ "generatorConfig-article.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
ArticleGenerator generatorSqlmap = new ArticleGenerator();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
service-article com/imooc/article/mapper/CommentsMapper.java
package com.imooc.article.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Comments;
public interface CommentsMapper extends MyMapper<Comments> {
}
================================================================
service-article resources/mapper/CommentsMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.article.mapper.CommentsMapper">
<resultMap id="BaseResultMap" type="com.imooc.pojo.Comments">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="writer_id" jdbcType="VARCHAR" property="writerId" />
<result column="father_id" jdbcType="VARCHAR" property="fatherId" />
<result column="article_id" jdbcType="VARCHAR" property="articleId" />
<result column="article_title" jdbcType="VARCHAR" property="articleTitle" />
<result column="article_cover" jdbcType="VARCHAR" property="articleCover" />
<result column="comment_user_id" jdbcType="VARCHAR" property="commentUserId" />
<result column="comment_user_nickname" jdbcType="VARCHAR" property="commentUserNickname" />
<result column="comment_user_face" jdbcType="VARCHAR" property="commentUserFace" />
<result column="content" jdbcType="VARCHAR" property="content" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
<resultMap id="BaseResultMap" type="com.imooc.pojo.Comments">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="writer_id" jdbcType="VARCHAR" property="writerId" />
<result column="father_id" jdbcType="VARCHAR" property="fatherId" />
<result column="article_id" jdbcType="VARCHAR" property="articleId" />
<result column="article_title" jdbcType="VARCHAR" property="articleTitle" />
<result column="article_cover" jdbcType="VARCHAR" property="articleCover" />
<result column="comment_user_id" jdbcType="VARCHAR" property="commentUserId" />
<result column="comment_user_nickname" jdbcType="VARCHAR" property="commentUserNickname" />
<result column="comment_user_face" jdbcType="VARCHAR" property="commentUserFace" />
<result column="content" jdbcType="VARCHAR" property="content" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
<resultMap id="BaseResultMap" type="com.imooc.pojo.Comments">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="writer_id" jdbcType="VARCHAR" property="writerId" />
<result column="father_id" jdbcType="VARCHAR" property="fatherId" />
<result column="article_id" jdbcType="VARCHAR" property="articleId" />
<result column="article_title" jdbcType="VARCHAR" property="articleTitle" />
<result column="article_cover" jdbcType="VARCHAR" property="articleCover" />
<result column="comment_user_id" jdbcType="VARCHAR" property="commentUserId" />
<result column="comment_user_nickname" jdbcType="VARCHAR" property="commentUserNickname" />
<result column="content" jdbcType="VARCHAR" property="content" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
</mapper>
service-api com/imooc/api/controller/article/CommentControllerApi.java
@Api(value = "评论相关业务的controller", tags = {"评论相关业务的controller"})
@RequestMapping("comment")
public interface CommentControllerApi {
@PostMapping("createComment")
@ApiOperation(value = "用户评论", notes = "用户评论", httpMethod = "POST") //@Valid是做验证的
public GraceJSONResult createArticle(@RequestBody @Valid CommentReplyBO commentReplyBO, BindingResult result);
service-article com/imooc/article/controller/CommentController.java
@RestController
public class CommentController extends BaseController implements CommentControllerApi {
final static Logger logger = LoggerFactory.getLogger(CommentController.class);
@Override
public GraceJSONResult createArticle(@Valid CommentReplyBO commentReplyBO,
BindingResult result) {
// 0. 判断BindingResult是否保存错误的验证信息,如果有,则直接return
if (result.hasErrors()) {
Map<String, String> errorMap = getErrors(result);
return GraceJSONResult.errorMap(errorMap);
}
// 1. 根据留言用户的id查询他的昵称,用于存入到数据表进行字段的冗余处理,从而避免多表关联查询的性能影响
String userId = commentReplyBO.getCommentUserId();
// 2. 发起restTemplate调用用户服务,获得用户侧昵称
Set<String> idSet = new HashSet<>();
idSet.add(userId);
String nickname = getBasicUserList(idSet).get(0).getNickname();
...[未完待续]
service-api com/imooc/api/BaseController.java
public List<AppUserVO> getBasicUserList(Set idSet) {
String userServerUrlExecute
= "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> userVOList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
userVOList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return userVOList;
}
dev-model com/imooc/pojo/bo/CommentReplyBO.java
/**
* 文章留言的BO
*/
public class CommentReplyBO {
@NotBlank(message = "留言信息不完整")
private String articleId;
@NotBlank(message = "留言信息不完整")
private String fatherId;
@NotBlank(message = "当前用户信息不正确,请尝试重新登录")
private String commentUserId;
@NotBlank(message = "留言内容不能为空")
@Length(max = 50, message = "文章内容长度不能超过50")
private String content;
}Getter + Setter + ToString
dev-model com/imooc/pojo/Comments.java
public class Comments {
@Id
private String id;
/**
* 评论的文章是哪个作者的关联id
*/
@Column(name = "writer_id")
private String writerId;
/**
* 如果是回复留言,则本条为子留言,需要关联查询
*/
@Column(name = "father_id")
private String fatherId;
/**
* 回复的那个文章id
*/
@Column(name = "article_id")
private String articleId;
/**
* 冗余文章标题,宽表处理,非规范化的sql思维,对于几百万文章和几百万评论的关联查询来讲,性能肯定不行,所以做宽表处理,从业务角度来说,文章发布以后不能随便修改标题和封面的
*/
@Column(name = "article_title")
private String articleTitle;
/**
* 文章封面
*/
@Column(name = "article_cover")
private String articleCover;
/**
* 发布留言的用户id
*/
@Column(name = "comment_user_id")
private String commentUserId;
/**
* 冗余用户昵称,非一致性字段,用户修改昵称后可以不用同步
*/
@Column(name = "comment_user_nickname")
private String commentUserNickname;
/**
* 冗余的用户头像
*/
@Column(name = "comment_user_face")
private String commentUserFace;
/**
* 留言内容
*/
private String content;
/**
* 留言时间
*/
@Column(name = "create_time")
private Date createTime;
用户评论入库保存【文章评论】这里暂时把数据库的comment_user_face删除了
service-api com/imooc/api/controller/article/CommentControllerApi.java
@Api(value = "评论相关业务的controller", tags = {"评论相关业务的controller"})
@RequestMapping("comment")
public interface CommentControllerApi {
@PostMapping("createComment")
@ApiOperation(value = "用户评论", notes = "用户评论", httpMethod = "POST") //@Valid是做验证的
public GraceJSONResult createArticle(@RequestBody @Valid CommentReplyBO commentReplyBO, BindingResult result);
}
service-article com/imooc/article/controller/CommentController.java
@RestController
public class CommentController extends BaseController implements CommentControllerApi {
final static Logger logger = LoggerFactory.getLogger(CommentController.class);
@Autowired
private CommentPortalService commentPortalService;
@Override
public GraceJSONResult createArticle(@Valid CommentReplyBO commentReplyBO,
BindingResult result) {
// 0. 判断BindingResult是否保存错误的验证信息,如果有,则直接return
if (result.hasErrors()) {
Map<String, String> errorMap = getErrors(result);
return GraceJSONResult.errorMap(errorMap);
}
// 1. 根据留言用户的id查询他的昵称,用于存入到数据表进行字段的冗余处理,从而避免多表关联查询的性能影响
String userId = commentReplyBO.getCommentUserId();
// 2. 发起restTemplate调用用户服务,获得用户侧昵称
Set<String> idSet = new HashSet<>();
idSet.add(userId);
String nickname = getBasicUserList(idSet).get(0).getNickname();
// 3. 保存用户评论的信息到数据库
commentPortalService.createComment(commentReplyBO.getArticleId(), commentReplyBO.getFatherId(), commentReplyBO.getContent(), userId, nickname);
return GraceJSONResult.ok();
}
}
service-article com/imooc/article/service/CommentPortalService.java
public interface CommentPortalService {
/**
* 发表评论
*/
public void createComment(String articleId,
String fatherCommentId,
String content,
String userId,
String nickname);
}
service-article com/imooc/article/service/impl/CommentPortalServiceImpl.java
package com.imooc.article.service.impl;
import com.imooc.api.service.BaseService;
import com.imooc.article.mapper.CommentsMapper;
import com.imooc.article.service.ArticlePortalService;
import com.imooc.article.service.CommentPortalService;
import com.imooc.pojo.Comments;
import com.imooc.pojo.vo.ArticleDetailVO;
import com.imooc.utils.PagedGridResult;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Service
public class CommentPortalServiceImpl extends BaseService implements CommentPortalService {
@Autowired
private ArticlePortalService articlePortalService;
@Autowired
private Sid sid;
@Autowired
private CommentsMapper commentsMapper;
@Transactional
@Override
public void createComment(String articleId, String fatherCommentId, String content, String userId, String nickname) {
String commentId = sid.nextShort();
ArticleDetailVO article = articlePortalService.queryDetail(articleId);
Comments comments = new Comments();
comments.setId(commentId);
comments.setWriterId(article.getPublishUserId());
comments.setArticleTitle(article.getTitle());
comments.setArticleCover(article.getCover());
comments.setArticleId(articleId);
comments.setFatherId(fatherCommentId);
comments.setCommentUserId(userId);
comments.setCommentUserNickname(nickname);
comments.setContent(content);
comments.setCreateTime(new Date());
commentsMapper.insert(comments);
// 评论数累加
redis.increment(REDIS_ARTICLE_COMMENT_COUNTS + ":" + articleId, 1);
}
}
dev-model com/imooc/pojo/Comments.java
public class Comments {
@Id
private String id;
/**
* 评论的文章是哪个作者的关联id
*/
@Column(name = "writer_id")
private String writerId;
/**
* 如果是回复留言,则本条为子留言,需要关联查询
*/
@Column(name = "father_id")
private String fatherId;
/**
* 回复的那个文章id
*/
@Column(name = "article_id")
private String articleId;
/**
* 冗余文章标题,宽表处理,非规范化的sql思维,对于几百万文章和几百万评论的关联查询来讲,性能肯定不行,所以做宽表处理,从业务角度来说,文章发布以后不能随便修改标题和封面的
*/
@Column(name = "article_title")
private String articleTitle;
/**
* 文章封面
*/
@Column(name = "article_cover")
private String articleCover;
/**
* 发布留言的用户id
*/
@Column(name = "comment_user_id")
private String commentUserId;
/**
* 冗余用户昵称,非一致性字段,用户修改昵称后可以不用同步
*/
@Column(name = "comment_user_nickname")
private String commentUserNickname;
// /**
// * 冗余的用户头像
// */
// @Column(name = "comment_user_face")
// private String commentUserFace;
/**
* 留言内容
*/
private String content;
/**
* 留言时间
*/
@Column(name = "create_time")
private Date createTime;
}
service-api com/imooc/api/BaseController.java
public List<AppUserVO> getBasicUserList(Set idSet) {
String userServerUrlExecute
= "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> userVOList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
userVOList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return userVOList;
}
service-article com/imooc/article/mapper/CommentsMapper.java
package com.imooc.article.mapper;
import com.imooc.my.mapper.MyMapper;
import com.imooc.pojo.Comments;
import org.springframework.stereotype.Repository;
@Repository
public interface CommentsMapper extends MyMapper<Comments> {
}
service-article resources/mapper/CommentsMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.CommentsMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.Comments" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="writer_id" property="writerId" jdbcType="VARCHAR" />
<result column="father_id" property="fatherId" jdbcType="VARCHAR" />
<result column="article_id" property="articleId" jdbcType="VARCHAR" />
<result column="article_title" property="articleTitle" jdbcType="VARCHAR" />
<result column="article_cover" property="articleCover" jdbcType="VARCHAR" />
<result column="comment_user_id" property="commentUserId" jdbcType="VARCHAR" />
<result column="comment_user_nickname" property="commentUserNickname" jdbcType="VARCHAR" />
<result column="content" property="content" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
</resultMap>
</mapper>
评论数累计与显示【文章评论】
service-api com/imooc/api/controller/article/CommentControllerApi.java
@Api(value = "评论相关业务的controller", tags = {"评论相关业务的controller"})
@RequestMapping("comment")
public interface CommentControllerApi {
@GetMapping("counts")
@ApiOperation(value = "用户评论数查询", notes = "用户评论数查询", httpMethod = "GET")
public GraceJSONResult commentCounts(@RequestParam String articleId);
}
service-article com/imooc/article/controller/CommentController.java
@Override
public GraceJSONResult commentCounts(String articleId) {
Integer counts = getCountsFromRedis(REDIS_ARTICLE_COMMENT_COUNTS + ":" + articleId);
return GraceJSONResult.ok(counts);
}
--------------------------------------------------------------------------
service-api com/imooc/api/BaseController.java
public Integer getCountsFromRedis(String key){
String countsStr = redis.get(key);
if (StringUtils.isBlank(countsStr)) {
countsStr = "0";
}
return Integer.valueOf(countsStr);
}
文章评论sql关联查询father_id…
【多表关联查询】
SELECT
c.id as commentId,
c.father_id as fatherId,
c.comment_user_id as commentUserId,
c.comment_user_nickname as commentUserNickname,
c.article_id as articleId,
c.content as content,
c.create_time as createTime,
f.comment_user_nickname as quoteUserNickname,
f.content as quoteContent
FROM
comments c
LEFT JOIN
comments f
ON
c.father_id = f.id
WHERE
c.article_id = '2006117B57WRZGHH'
ORDER BY
c.create_time
DESC
显示评论列表【文章评论】
service-api com/imooc/api/controller/article/CommentControllerApi.java
com/imooc/api/controller/article/CommentControllerApi.java
@GetMapping("list")
@ApiOperation(value = "查询文章的所有评论列表", notes = "查询文章的所有评论列表", httpMethod = "GET")
public GraceJSONResult list(@RequestParam String articleId,
@RequestParam Integer page,
@RequestParam Integer pageSize);
service-article com/imooc/article/controller/CommentController.java
@Override
public GraceJSONResult list(String articleId, Integer page, Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult = commentPortalService.queryArticleComments(articleId, page, pageSize);
return GraceJSONResult.ok(gridResult);
}
==============================================================
http://www.imoocnews.com:9090/imooc-news/portal/detail.html?articleId=200816961ZYBXFRP
service-article com/imooc/article/service/CommentPortalService.java
/**
* 查询文章评论列表
*/
public PagedGridResult queryArticleComments(String articleId,
Integer page,
Integer pageSize);
service-article com/imooc/article/mapper/CommentsMapperCustom.java
package com.imooc.article.mapper;
import com.imooc.pojo.vo.CommentsVO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
@Repository
public interface CommentsMapperCustom {
/**
* 查询文章评论
*/
public List<CommentsVO> queryArticleCommentList(@Param("paramMap") Map<String, Object> map);
}
service-article com/imooc/article/service/impl/CommentPortalServiceImpl.java
@Override
public PagedGridResult queryArticleComments(String articleId, Integer page, Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("articleId", articleId);
PageHelper.startPage(page, pageSize);
List<CommentsVO> list = commentsMapperCustom.queryArticleCommentList(map);
return setterPagedGrid(list,page);
}
service-article resources/mapper/CommentsMapperCustom.xml #【把关于face的字段都删掉】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.CommentsMapperCustom" >
<select id="queryArticleCommentList"
resultType="com.imooc.pojo.vo.CommentsVO"
parameterType="Map">
SELECT
c.id as commentId,
c.father_id as fatherId,
c.comment_user_id as commentUserId,
c.comment_user_nickname as commentUserNickname,
c.article_id as articleId,
c.content as content,
c.create_time as createTime,
f.comment_user_nickname as quoteUserNickname,
f.content as quoteContent
FROM
comments c
LEFT JOIN
comments f
ON
c.father_id = f.id
WHERE
c.article_id = #{paramMap.articleId}
ORDER BY
c.create_time
DESC
</select>
</mapper>
(作业) 管理评论列表以及删除评论【评论管理】
前端的commentMng.html的VUE有问题
需要增加定义userInfo
var mainPage = new Vue({
el: “#mainPage”,
data: {
userInfo: {
activeStatus: 0
},
}…
service-api com/imooc/api/controller/article/CommentControllerApi.java
@PostMapping("mng")
@ApiOperation(value = "查询我的评论管理列表", notes = "查询我的评论管理列表", httpMethod = "POST")
public GraceJSONResult mng(@RequestParam String writerId,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize);
@PostMapping("/delete")
@ApiOperation(value = "作者删除评论", notes = "作者删除评论", httpMethod = "POST")
public GraceJSONResult delete(@RequestParam String writerId,
@RequestParam String commentId);
service-article com/imooc/article/controller/CommentController.java
@Override
public GraceJSONResult mng(String writerId, Integer page, Integer pageSize) {
if (page == null) {
page = COMMON_START_PAGE;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult gridResult = commentPortalService.queryWriterCommentsMng(writerId, page, pageSize);
return GraceJSONResult.ok(gridResult);
}
@Override
public GraceJSONResult delete(String writerId, String commentId) {
commentPortalService.deleteComment(writerId, commentId);
return GraceJSONResult.ok();
}
==============================================================
http://writer.imoocnews.com:9090/imooc-news/writer/commentMng.html
service-article com/imooc/article/service/CommentPortalService.java
/**
* 查询我的评论管理列表
*/
public PagedGridResult queryWriterCommentsMng(String writerId, Integer page, Integer pageSize);
/**
* 删除评论
*/
public void deleteComment(String writerId, String commentId);
service-article com/imooc/article/service/impl/CommentPortalServiceImpl.java
@Override
public PagedGridResult queryWriterCommentsMng(String writerId, Integer page, Integer pageSize) {
Comments comment = new Comments();
comment.setWriterId(writerId);
PageHelper.startPage(page, pageSize);
List<Comments> list = commentsMapper.select(comment);
return setterPagedGrid(list,page);
}
@Override
public void deleteComment(String writerId, String commentId) {
Comments comment = new Comments();
comment.setId(commentId);
comment.setWriterId(writerId);
commentsMapper.delete(comment);
}
增加评论者头像展示功能需求扩展【文章评论】增加需求字段comment_user_face
[数据库添加一个新的字段comment_user_face 重新在mybatis-generator-database进行逆向生成覆盖]
涉及范围广在数据库里也要加个字段 在前端需求也要改一下头像
detail.html
<div class="all-comments-list" v-for="(comment,index) in commentList" :key="index">
<div class="single-comment-wrapper">
<!--<img src="./img/face1.png" class="user-face"/>-->
<img :src="comment.commentUserFace" class="user-face"/>
</div>
service-article resources/mapper/CommentsMapper.xml #【增加字段】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.CommentsMapper" >
<resultMap id="BaseResultMap" type="com.imooc.pojo.Comments" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="writer_id" property="writerId" jdbcType="VARCHAR" />
<result column="father_id" property="fatherId" jdbcType="VARCHAR" />
<result column="article_id" property="articleId" jdbcType="VARCHAR" />
<result column="article_title" property="articleTitle" jdbcType="VARCHAR" />
<result column="article_cover" property="articleCover" jdbcType="VARCHAR" />
<result column="comment_user_id" property="commentUserId" jdbcType="VARCHAR" />
<result column="comment_user_nickname" property="commentUserNickname" jdbcType="VARCHAR" />
<result column="comment_user_face" property="commentUserFace" jdbcType="VARCHAR" />
<result column="content" property="content" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
</resultMap>
</mapper>
service-article resources/mapper/CommentsMapperCustom.xml #【增加字段】
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imooc.article.mapper.CommentsMapperCustom" >
<select id="queryArticleCommentList"
resultType="com.imooc.pojo.vo.CommentsVO"
parameterType="Map">
SELECT
c.id as commentId,
c.father_id as fatherId,
c.comment_user_id as commentUserId,
c.comment_user_nickname as commentUserNickname,
c.comment_user_face as commentUserFace,
c.article_id as articleId,
c.content as content,
c.create_time as createTime,
f.comment_user_nickname as quoteUserNickname,
f.content as quoteContent
FROM
comments c
LEFT JOIN
comments f
ON
c.father_id = f.id
WHERE
c.article_id = #{paramMap.articleId}
ORDER BY
c.create_time
DESC
</select>
</mapper>
service-article com/imooc/article/service/CommentPortalService.java //【增加字段】
/**
* 发表评论
*/
public void createComment(String articleId,
String fatherCommentId,
String content,
String userId,
String nickname,
String face);
------------------------------------------------------------
dev-model com/imooc/pojo/vo/CommentsVO.java //【增加字段属性】
private String commentUserFace;
【Getter + Setter】
------------------------------------------------------------
dev-model com/imooc/pojo/Comments.java
/**
* 冗余的用户头像
*/
@Column(name = "comment_user_face")
private String commentUserFace;
【Getter + Setter】
------------------------------------------------------------
service-article com/imooc/article/controller/CommentController.java
//【增加 String face = getBasicUserList(idSet).get(0).getFace();】
@Override
public GraceJSONResult createArticle(@Valid CommentReplyBO commentReplyBO,
BindingResult result) {
// 0. 判断BindingResult是否保存错误的验证信息,如果有,则直接return
if (result.hasErrors()) {
Map<String, String> errorMap = getErrors(result);
return GraceJSONResult.errorMap(errorMap);
}
// 1. 根据留言用户的id查询他的昵称,用于存入到数据表进行字段的冗余处理,从而避免多表关联查询的性能影响
String userId = commentReplyBO.getCommentUserId();
// 2. 发起restTemplate调用用户服务,获得用户侧昵称
Set<String> idSet = new HashSet<>();
idSet.add(userId);
String nickname = getBasicUserList(idSet).get(0).getNickname();
String face = getBasicUserList(idSet).get(0).getFace();
// 3. 保存用户评论的信息到数据库
commentPortalService.createComment(commentReplyBO.getArticleId(), commentReplyBO.getFatherId(), commentReplyBO.getContent(), userId, nickname,face);
return GraceJSONResult.ok();
}
====================================================
http://www.imoocnews.com:9090/imooc-news/portal/detail.html?articleId=2006116Z3MAP8SW0
//下面有个评论:牛逼 带着自己上传的头像
service-article com/imooc/article/service/impl/CommentPortalServiceImpl.java
//【增加 comments.setCommentUserFace(face);】
@Transactional
@Override
public void createComment(String articleId, String fatherCommentId, String content, String userId, String nickname,String face) {
String commentId = sid.nextShort();
ArticleDetailVO article = articlePortalService.queryDetail(articleId);
Comments comments = new Comments();
comments.setId(commentId);
comments.setWriterId(article.getPublishUserId());
comments.setArticleTitle(article.getTitle());
comments.setArticleCover(article.getCover());
comments.setArticleId(articleId);
comments.setFatherId(fatherCommentId);
comments.setCommentUserId(userId);
comments.setCommentUserNickname(nickname);
comments.setCommentUserFace(face);
comments.setContent(content);
comments.setCreateTime(new Date());
commentsMapper.insert(comments);
// 评论数累加
redis.increment(REDIS_ARTICLE_COMMENT_COUNTS + ":" + articleId, 1);
}
文章静态化技术与Freemarker【文章概述】
- 页面静态化
- Freemarker静态化技术
- 渲染模板数据
- 生成并展示静态页面
静态化趋势
- 便于SEO
- 加速用户访问
- 降低数据库压力
模板引擎技术
- JSP
- Freemarker
- Thymeleaf
- Velocity
页面静态化
创建并且显示模板ftl
service-article pom.xml
<!-- freemarker 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
service-article com/imooc/article/controller/FreemarkerController.java
package com.imooc.article.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping("free")
public class FreemarkerController{
@GetMapping("/hello")
public String hello(Model model){
// 定义输出到模板的内容
// 输入字符串
String stranger = "慕课网 imooc.com";
model.addAttribute("there", stranger);
// 返回的stu是freemarker模板所在的目录 classpath:/templates/
// 匹配 *.ftl
return "stu";
}
}
==================================================================
http://localhost:8001/free/hello
service-article resources/application.yml 【suffix是模板后缀】
freemarker:
charset: UTF-8
content-type: text/html
suffix: .ftl
template-loader-path: classpath:/templates/
service-article resources/templates/stu.ftl
<html>
<head>
<title>Hello Freemarker</title>
</head>
<body>
<#--
写完以后去模板页面配置 application.yml
Freemarker 页面的语法构成:
1. 注释
2. 表达式 ${...}
3. 普通文本,基本的html标签
4. 指令
-->
<div>
hello ${there}
</div>
</body>
</html>
输出对象【Freemarker语法】
dev-model com/imooc/pojo/Stu.java
public class Stu {
private String uid;
private String username;
private Integer age;
private Date birthday;
private Float amount;
private boolean haveChild;
private Spouse spouse;
} Getter + Setter
dev-model com/imooc/pojo/Spouse.java
public class Spouse {
private String username;
private Integer age;
} Getter + Setter
service-article com/imooc/article/controller/FreemarkerController.java
package com.imooc.article.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping("free")
public class FreemarkerController{
@GetMapping("/hello")
public String hello(Model model){
// 定义输出到模板的内容
// 输入字符串
String stranger = "慕课网 imooc.com";
model.addAttribute("there", stranger);
makeModel(model);
// 返回的stu是freemarker模板所在的目录 classpath:/templates/
// 匹配 *.ftl
return "stu";
}
private Model makeModel(Model model) {
Stu stu = new Stu();
stu.setUid("10010");
stu.setUsername("imooc");
stu.setAmount(88.86f);
stu.setAge(18);
stu.setHaveChild(true);
stu.setBirthday(new Date());
Spouse spouse = new Spouse();
spouse.setUsername("Lucy");
spouse.setAge(25);
stu.setSpouse(spouse);
model.addAttribute("stu",stu);
return model;
}
}
==================================================================
http://localhost:8001/free/hello
Hello 慕课网 imooc.com
用户名uid: 10010
用户姓名: imooc
年龄:18
生日:2024-07-29 15:13:05
用户余额:88.86
已育:yes
伴侣:Lucy,25岁
service-article resources/templates/stu.ftl
<html>
<head>
<title>Hello Freemarker</title>
</head>
<body>
<#-- 写完以后去模板页面配置 application.yml
Freemarker 页面的语法构成:
1. 注释
2. 表达式 ${...}
3. 普通文本,基本的html标签
4. 指令
-->
<div>
hello ${there}
</div>
<br>
<div>
用户名uid: ${stu.uid}<br>
用户姓名: ${stu.username}<br>
年龄:${stu.age}<br>
生日:${stu.birthday?string('yyyy-MM-dd HH:mm:ss')}<br> <#-- 日期转换 -->
用户余额:${stu.amount}<br>
已育:${stu.haveChild?string('yes', 'no')}<br>
伴侣:${stu.spouse.username},${stu.spouse.age}岁
</div>
</body>
</html>
输出list与map【Freemarker语法】
dev-model com/imooc/pojo/Stu.java
public class Stu {
private String uid;
private String username;
private Integer age;
private Date birthday;
private Float amount;
private boolean haveChild;
private Spouse spouse;
private List<Article> articleList;
private Map<String, String> parents;
} Getter + Setter
dev-model com/imooc/pojo/Spouse.java
public class Spouse {
private String username;
private Integer age;
} Getter + Setter
service-article com/imooc/article/controller/FreemarkerController.java
package com.imooc.article.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.Article;
import com.imooc.pojo.Spouse;
import com.imooc.pojo.Stu;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@Controller
@RequestMapping("free")
public class FreemarkerController{
@GetMapping("/hello")
public String hello(Model model){
// 定义输出到模板的内容
// 输入字符串
String stranger = "慕课网 imooc.com";
model.addAttribute("there", stranger);
makeModel(model);
// 返回的stu是freemarker模板所在的目录 classpath:/templates/
// 匹配 *.ftl
return "stu";
}
private Model makeModel(Model model) {
Stu stu = new Stu();
stu.setUid("10010");
stu.setUsername("imooc");
stu.setAmount(88.86f);
stu.setAge(18);
stu.setHaveChild(true);
stu.setBirthday(new Date());
Spouse spouse = new Spouse();
spouse.setUsername("Lucy");
spouse.setAge(25);
stu.setSpouse(spouse);
stu.setArticleList(getArticles());
stu.setParents(getParents());
model.addAttribute("stu",stu);
return model;
}
private List<Article> getArticles(){
Article article1 = new Article();
article1.setId("1001");
article1.setTitle("今天天气不错");
Article article2 = new Article();
article2.setId("1002");
article2.setTitle("今天下雨了");
Article article3 = new Article();
article3.setId("1003");
article3.setTitle("昨天下雨了");
List<Article> list = new ArrayList<>();
list.add(article1);
list.add(article2);
list.add(article3);
return list;
}
private Map<String, String> getParents(){
Map<String, String> parents = new HashMap<>();
parents.put("father", "XiaoMing");
parents.put("mother", "LiLi");
return parents;
}
}
==================================================================
http://localhost:8001/free/hello
hello 慕课网 imooc.com
用户名uid: 10010
用户姓名: imooc
年龄:18
生日:2024-07-29 15:39:07
用户余额:88.86
已育:yes
伴侣:Lucy,25岁
1001 今天天气不错
1002 今天下雨了
1003 昨天下雨了
LiLi
XiaoMing
service-article resources/templates/stu.ftl
<html>
<head>
<title>Hello Freemarker</title>
</head>
<body>
<#-- 写完以后去模板页面配置 application.yml
Freemarker 页面的语法构成:
1. 注释
2. 表达式 ${...}
3. 普通文本,基本的html标签
4. 指令
-->
<div>
hello ${there}
</div>
<br>
<div>
用户名uid: ${stu.uid}<br>
用户姓名: ${stu.username}<br>
年龄:${stu.age}<br>
生日:${stu.birthday?string('yyyy-MM-dd HH:mm:ss')}<br> <#-- 日期转换 -->
用户余额:${stu.amount}<br>
已育:${stu.haveChild?string('yes', 'no')}<br>
伴侣:${stu.spouse.username},${stu.spouse.age}岁
</div>
<br>
<div>
<#list stu.articleList as article>
<div>
<span>${article.id}</span>
<span>${article.title}</span>
</div>
</#list>
</div>
<br>
<div>
<#list stu.parents?keys as key>
<div>
${stu.parents[key]}
</div>
</#list>
</div>
</body>
</html>
指令if【Freemarker语法】
service-article resources/templates/stu.ftl
<html>
<head>
<title>Hello Freemarker</title>
</head>
<body>
<#--
写完以后去模板页面配置 application.yml
Freemarker 页面的语法构成:
1. 注释
2. 表达式 ${...}
3. 普通文本,基本的html标签
4. 指令
-->
<div>
hello ${there}
</div>
<br>
<div>
用户名uid: ${stu.uid}<br>
用户姓名: ${stu.username}<br>
年龄:${stu.age}<br>
生日:${stu.birthday?string('yyyy-MM-dd HH:mm:ss')}<br> <#-- 日期转换 -->
用户余额:${stu.amount}<br>
已育:${stu.haveChild?string('yes', 'no')}<br>
<#if stu.spouse??>
伴侣:${stu.spouse.username}, ${stu.spouse.age}岁
</#if>
<#if !stu.spouse??>
单身狗
</#if>
</div>
<br>
<div>
<#list stu.articleList as article>
<div>
<span>${article.id}</span>
<span>${article.title}</span>
</div>
</#list>
</div>
<br>
<div>
<#list stu.parents?keys as key>
<div>
${stu.parents[key]}
</div>
</#list>
</div>
<br>
<div>
<#if stu.uid == '10010'>
用户id是10010
<br>
</#if>
<#if stu.username != 'imooc'>
用户名不是imooc
<br>
</#if>
<#if (stu.age >= 18) >
用户已成年
</#if>
<br>
<#if (stu.age > 18 || stu.age = 18) >
成年人
<br>
</#if>
<#if (stu.age < 18) >
未成年
<br>
</#if>
<#if stu.haveChild >
已育
</#if>
<br>
<#if !stu.haveChild >
未育
</#if>
</div>
</body>
</html>
在这里特别注意一下 已经开始第二阶段的代码 进阶篇 所以前端的代码也是需要更新换代的 包括../js/app.js里面多了 app.getPageName();
没有getPageName这个函数-慕课网 (imooc.com)
生成的html调用app.js中getPageName()函数出错的问题-慕课网 (imooc.com)
结合动态数据生成静态化HTML【Freemarker】
service-article com/imooc/article/controller/FreemarkerController.java
【stu.ftl如上图不变增加java的整合代码】 俗称Java+ftl=HTML
package com.imooc.article.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.Article;
import com.imooc.pojo.Spouse;
import com.imooc.pojo.Stu;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
@Controller
@RequestMapping("free")
public class FreemarkerController{
@Value("${freemarker.html.target}")
private String htmlTarget;
@GetMapping("/createHTML")
@ResponseBody
public String createHTML(Model model) throws IOException, TemplateException {
// 0. 配置freemarker基本环境
Configuration cfg = new Configuration(Configuration.getVersion());
// 声明freemarker模板所需要加载的目录的位置
//resources/templates/stu.ftl
String classpath = this.getClass().getResource("/").getPath();
cfg.setDirectoryForTemplateLoading(new File((classpath + "templates")));
// 测试打印
System.out.println(htmlTarget);
System.out.println(classpath + "templates");
/**
* /workspace/freemarker_html
* /C:/Users/Pluminary/Desktop/backup/imooc-news-dev/imooc-news-dev-service-article/target/classes/templates
*/
// 1. 获得现有的模板ftl文件
Template template = cfg.getTemplate("stu.ftl", "utf-8");
// 2. 获得动态数据
// 定义输出到模板的内容
// 输入字符串
String stranger = "慕课网 imooc.com";
model.addAttribute("there", stranger);
model = makeModel(model);
// 3. 融合动态数据和ftl,生成html
File tempDic = new File(htmlTarget);
if (!tempDic.exists()) {
tempDic.mkdirs();
}
Writer out = new FileWriter(htmlTarget + File.separator + "10010" + ".html");
template.process(model, out);
out.close();
return "ok";
// C:\workspace\freemarker_html\10010.html 里面的数据都是静态数据
}
@GetMapping("/hello")
public String hello(Model model){
makeModel(model);
// 返回的stu是freemarker模板所在的目录 classpath:/templates/
// 匹配 *.ftl
return "stu";
}
private Model makeModel(Model model) {
Stu stu = new Stu();
stu.setUid("10010");
stu.setUsername("imooc");
stu.setAmount(88.86f);
stu.setAge(18);
stu.setHaveChild(true);
stu.setBirthday(new Date());
Spouse spouse = new Spouse();
spouse.setUsername("Lucy");
spouse.setAge(25);
stu.setSpouse(spouse);
stu.setArticleList(getArticles());
stu.setParents(getParents());
model.addAttribute("stu",stu);
return model;
}
private List<Article> getArticles(){
Article article1 = new Article();
article1.setId("1001");
article1.setTitle("今天天气不错");
Article article2 = new Article();
article2.setId("1002");
article2.setTitle("今天下雨了");
Article article3 = new Article();
article3.setId("1003");
article3.setTitle("昨天下雨了");
List<Article> list = new ArrayList<>();
list.add(article1);
list.add(article2);
list.add(article3);
return list;
}
private Map<String, String> getParents(){
Map<String, String> parents = new HashMap<>();
parents.put("father", "XiaoMing");
parents.put("mother", "LiLi");
return parents;
}
}
改写详情页为模板页ftl【页面静态化】
地址页不是拼接 将detail.html 的路径都变成绝对路径
<link rel="shortcut icon" href="../img/mu-toutiao.ico" />
把其拷贝到 resources/templates/detail.ftl
【放在d盘 tomcat/webapps中的】detail.ftl
<div class="big-title">
${articleDetail.title}
</div>
<div class="read-counts" v-show="articleDetail.readCounts != '' && articleDetail.readCounts != null">
阅读量:${articleDetail.readCounts}
</div>
......
<div class="date-title">
<span class="year">${articleDetail.publishTime?string('yyyy')}</span>
</div>
<div class="back-year-line"></div>
<div class="date-md">${articleDetail.publishTime?string('MM/dd')}</div>
<div class="date-times">${articleDetail.publishTime?string('HH:mm:ss')}</div>
<div class="writer-name" @click="showWriter('${articleDetail.publishUserId}')">
${articleDetail.publishUserName}
</div>
.....
<div class="article-wrapper">
<div class="content">
${articleDetail.content}
</div>
<div class="declare">
免责声明:本平台所有内容仅供测试,且文章来自互联网,不代表慕课网的观点和立场,如有不妥,请联系后删除。
</div>
</div>
文章详情ftl生成静态化页面【页面静态化】
freemarker:
html:
target: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
article: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
dev-model com/imooc/pojo/vo/ArticleDetailVO.java
public class ArticleDetailVO {
private String id;
private String title;
private String cover;
private Integer categoryId;
private String categoryName;
private String publishUserId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishTime;
private String content;
private String publishUserName;
private Integer readCounts;
}Getter + Setter
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult doReview(String articleId, Integer passOrNot) {
Integer pendingStatus;
if (passOrNot == YesOrNo.YES.type) {
// 审核成功
pendingStatus = ArticleReviewStatus.SUCCESS.type;
} else if (passOrNot == YesOrNo.NO.type) {
// 审核失败
pendingStatus = ArticleReviewStatus.FAILED.type;
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
// 保存到数据库,更改文章状态为审核成功或者失败
articleService.updateArticleStatus(articleId, pendingStatus);
if (pendingStatus == ArticleReviewStatus.SUCCESS.type){
//审核成功,生成文章详情页静态html
try{
createArticleHTML(articleId);
// String articleMongoId = createArticleHTMLToGridF(articleId);
}catch (Exception e){
e.printStackTrace();
}
}
return GraceJSONResult.ok();
}
@Value("${freemarker.html.target}")
private String articlePath;
@Autowired
private RestTemplate restTemplate;
// 文章生成HTML
public void createArticleHTML(String articleId) throws IOException, TemplateException {
Configuration cfg = new Configuration(Configuration.getVersion());
String classpath = this.getClass().getResource("/").getPath();
cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));
Template template = cfg.getTemplate("detail.ftl", "utf-8");
// 获得文章的详情数据
ArticleDetailVO detailVO = getArticleDetail(articleId);
Map<String, Object> map = new HashMap<>();
map.put("articleDetail", detailVO);
File tempDic = new File(articlePath);
if (!tempDic.exists()) {
tempDic.mkdirs();
}
String path = articlePath + File.separator + detailVO.getId() + ".html";
Writer out = new FileWriter(path);
template.process(map, out);
out.close();
}
// 发起远程调用rest,获得文章详情数据
public ArticleDetailVO getArticleDetail(String articleId) {
String url
= "http://www.imoocnews.com:8001/portal/article/detail?articleId=" + articleId;
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(url, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
ArticleDetailVO detailVO = null;
if (bodyResult.getStatus() == 200) {
String detailJson = JsonUtils.objectToJson(bodyResult.getData());
detailVO = JsonUtils.jsonToPojo(detailJson, ArticleDetailVO.class);
}
return detailVO;
}
先去发表头条http://writer.imoocnews.com:9090/imooc-news/writer/createArticle.html
再去审核通过http://www.imoocnews.com:9090/imooc-news/admin/contentReview.html
此时运行后 就会有java+ftl=html静态页面在指定位置生成了
指定位置:D:\apache-tomcat-8.5.93\webapps\imooc-news\portal\a
生成了一个文件:240729D9S8683XP0.html 这里面就是刚刚发表头条的内容
此时下面的两个网站都可以打开同样的头条内容
http://www.imoocnews.com:9090/imooc-news/portal/a/240729D9S8683XP0.html
http://www.imoocnews.com:9090/imooc-news/portal/detail.html?articleId=240729D9S8683XP0
240729D9S8683XP0.html
<div class="writer-name" @click="showWriter('240629F21AK1BHX4')">
P_luminary
</div>
// 跳转作家页面
showWriter(writerId) {
window.open("../writer.html?writerId=" + writerId);
},
没有getPageName这个函数-慕课网 (imooc.com)
文章阅读量detail单独获取并展示 【页面静态化】
【去前面代入阅读量】
<div class="read-counts">
阅读量:{{readCounts}}
</div>
【先定义readCounts初始量为0】
var articleList = new Vue({
el: "#detailContainer",
data: {
nowReplyingFatherCommentId: 0, // 根据当前用户正在回复的父commentId进行页面的留言看展示或隐藏
userInfo: null,
articleId: "",
articleDetail: {},
readCounts: 0,
}
// 获得文章阅读数
this.getArticleReadCounts(articleId);
// 获得文章阅读数
getArticleReadCounts(articleId) {
var me = this;
var articleServerUrl = app.articleServerUrl;
axios.defaults.withCredentials = true;
axios.get(articleServerUrl + "/portal/article/readCounts?articleId=" + articleId)
.then(res => {
// console.log(JSON.stringify(res.data));
this.readCounts = res.data;
});
},
//★★★★★★★★★★★★★★★★★★★★ ★★★★★★★★★★★★★★★★★★★★★★★\\
然后把这个临时页面修改的地方 移动到后端service-article resources/templates/detail.ftl中
service-article com/imooc/article/controller/ArticlePortalController.java
@Override
public Integer readCounts(String articleId) {
return getCountsFromRedis(REDIS_ARTICLE_READ_COUNTS + ":" + articleId);
}
service-api com/imooc/api/controller/article/ArticlePortalControllerApi.java
@GetMapping("readCounts")
@ApiOperation(value = "获得文章阅读数", notes = "获得文章阅读数", httpMethod = "GET")
public Integer readCounts(@RequestParam String articleId);
在这里折腾一天终于好了 文章:a6 (imoocnews.com)
9-10
梳理生产端消费端与中间gridfs关系
静态化高度耦合
在本地电脑/同一台服务器 => 生成静态页面HTML →
(发布)前端
解耦静态化
后端服务器(生成静态页面HTML) => GridFS <= 前端服务器(前端HTML)
同时 后端服务器(生成静态页面HTML) → 前端服务器(前端HTML)
创建一个新的module => imooc-news-dev-service-article-html
把service-article中resources的application.yml /dev+prod.yml logback-spring.xml拷贝到article-html模块的resources中
############################################################
http://localhost:8002/hello
{
"status": 200,
"msg": "操作成功!",
"success": true,
"data": null
}
application.yml
############################################################
#
# article文章静态化服务
# web访问端口号 约定:8002
#
############################################################
server:
# port: 8003
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
profiles:
active: dev # yml中配置文件的环境配置, dev:开发环境, test:测试环境, prod:生产环境
application:
name: service-article-html
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
data:
mongodb:
uri: mongodb://root:root@192.168.170.135:27017
database: imooc-news
freemarker:
charset: UTF-8
content-type: text/html
suffix: .ftl
template-loader-path: classpath:/templates/
# 定义freemarker生成的HTML
freemarker:
html:
target: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
article: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
application-dev.yml
server:
port: 8002
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
## setup CN from java, This is resource
website:
domain-name: imoocnews.com
application-prod.yml
server:
port: 8002
spring:
redis:
database: 0
host: 47.98.225.105
port: 6379
service-article-html com/imooc/article/html/Application.java
package com.imooc.article.html;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
service-article-html com/imooc/article/html/controller/HelloController.java
package com.imooc.article.html.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello() {
return GraceJSONResult.ok();
}
}
生产端存储html道gridfs并关联文章表【静态化解耦】
静态化解耦步骤
- 生成html,并上传到gridfs中
- 获得mongoFileId,关联保存到文章表中
- 调用消费端,下载gridfs的html进行发布
service-article com/imooc/article/service/ArticleService.java
/**
* 关联文章和gridfs的html文件id
*/
public void updateArticleToGridFS(String articleId, String articleMongoId);
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Transactional
@Override
public void updateArticleToGridFS(String articleId, String articleMongoId) {
Article pendingArticle = new Article();
pendingArticle.setId(articleId);
pendingArticle.setMongoFileId(articleMongoId);
articleMapper.updateByPrimaryKeySelective(pendingArticle);
}
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult doReview(String articleId, Integer passOrNot) {
Integer pendingStatus;
if (passOrNot == YesOrNo.YES.type) {
// 审核成功
pendingStatus = ArticleReviewStatus.SUCCESS.type;
} else if (passOrNot == YesOrNo.NO.type) {
// 审核失败
pendingStatus = ArticleReviewStatus.FAILED.type;
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
// 保存到数据库,更改文章状态为审核成功或者失败
articleService.updateArticleStatus(articleId, pendingStatus);
if (pendingStatus == ArticleReviewStatus.SUCCESS.type){
//审核成功,生成文章详情页静态html
try{
// createArticleHTML(articleId);
String articleMongoId = createArticleHTMLToGridFS(articleId);
// 存储到对应的文章 进行关联保存
articleService.updateArticleToGridFS(articleId, articleMongoId);
}catch (Exception e){
e.printStackTrace();
}
}
return GraceJSONResult.ok();
}
......
...
@Value("${freemarker.html.target}")
private String articlePath;
@Autowired
private RestTemplate restTemplate;
// 文章生成HTML
public void createArticleHTML(String articleId) throws IOException, TemplateException {
Configuration cfg = new Configuration(Configuration.getVersion());
String classpath = this.getClass().getResource("/").getPath();
cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));
Template template = cfg.getTemplate("detail.ftl", "utf-8");
// 获得文章的详情数据
ArticleDetailVO detailVO = getArticleDetail(articleId);
Map<String, Object> map = new HashMap<>();
map.put("articleDetail", detailVO);
File tempDic = new File(articlePath);
if (!tempDic.exists()) {
tempDic.mkdirs();
}
String path = articlePath + File.separator + detailVO.getId() + ".html";
Writer out = new FileWriter(path);
template.process(map, out);
out.close();
}
@Autowired
private GridFSBucket gridFSBucket;
public String createArticleHTMLToGridFS(String articleId) throws IOException, TemplateException {
Configuration cfg = new Configuration(Configuration.getVersion());
String classpath = this.getClass().getResource("/").getPath();
cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));
Template template = cfg.getTemplate("detail.ftl", "utf-8");
// 获得文章的详情数据
ArticleDetailVO detailVO = getArticleDetail(articleId);
Map<String, Object> map = new HashMap<>();
map.put("articleDetail", detailVO);
String htmlContent = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
// System.out.println(htmlContent);
InputStream inputStream = IOUtils.toInputStream(htmlContent);
ObjectId fileId = gridFSBucket.uploadFromStream(detailVO.getId() + ".html",inputStream);
return fileId.toString();
}
service-article com/imooc/article/GridFSConfig.java
package com.imooc.article;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component //可以被容器访问到
public class GridFSConfig {
@Value("${spring.data.mongodb.database}")
private String mongodb;
@Bean
public GridFSBucket gridFSBucket(MongoClient mongoClient){
MongoDatabase mongoDatabase = mongoClient.getDatabase(mongodb);
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);//存入mongodatabase
return bucket;
}
}
消费端从gridfs下载HTML到tomcat【静态化解耦】
service-api com/imooc/api/controller/article/ArticleHTMLControllerApi.java
package com.imooc.api.controller.article;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.NewArticleBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
@Api(value = "静态化文章业务的controller", tags = {"静态化文章业务的controller"})
@RequestMapping("article/html")
public interface ArticleHTMLControllerApi {
@GetMapping("download")
@ApiOperation(value = "下载html", notes = "下载html", httpMethod = "GET")
public Integer download(String articleId, String articleMongoId) throws Exception;
}
article-html com/imooc/article/html/controller/ArticleHTMLController.java
package com.imooc.article.html.controller;
import com.imooc.api.controller.article.ArticleHTMLControllerApi;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.gridfs.GridFS;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
@RestController
public class ArticleHTMLController implements ArticleHTMLControllerApi {
final static Logger logger = LoggerFactory.getLogger(ArticleHTMLController.class);
@Autowired //相应的下载
private GridFSBucket gridFSBucket;
@Value("${freemarker.html.article}")
private String articlePath;
@Override
public Integer download(String articleId, String articleMongoId)
throws Exception {
// 拼接最终文件的保存的地址
String path = articlePath + File.separator + articleId + ".html";
// 获取文件流,定义存放的位置和名称
File file = new File(path);
// 创建输出流
OutputStream outputStream = new FileOutputStream(file);
// 执行下载
gridFSBucket.downloadToStream(new ObjectId(articleMongoId), outputStream);
return HttpStatus.OK.value();
}
}
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult doReview(String articleId, Integer passOrNot) {
Integer pendingStatus;
if (passOrNot == YesOrNo.YES.type) {
// 审核成功
pendingStatus = ArticleReviewStatus.SUCCESS.type;
} else if (passOrNot == YesOrNo.NO.type) {
// 审核失败
pendingStatus = ArticleReviewStatus.FAILED.type;
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
// 保存到数据库,更改文章状态为审核成功或者失败
articleService.updateArticleStatus(articleId, pendingStatus);
if (pendingStatus == ArticleReviewStatus.SUCCESS.type){
//审核成功,生成文章详情页静态html
try{
// createArticleHTML(articleId);
String articleMongoId = createArticleHTMLToGridFS(articleId);
// 存储到对应的文章 进行关联保存
articleService.updateArticleToGridFS(articleId, articleMongoId);
// 调用消费端,执行下载html
doDownloadArticleHTML(articleId,articleMongoId);
}catch (Exception e){
e.printStackTrace();
}
}
return GraceJSONResult.ok();
}
private void doDownloadArticleHTML(String articleId, String articleMongoId) {
String url = //去SwitchHost弄个新的端口映射
"http://html.imoocnews.com:8002/article/html/download?articleId="
+ articleId +
"&articleMongoId="
+ articleMongoId;
ResponseEntity<Integer> responseEntity = restTemplate.getForEntity(url, Integer.class);
int status = responseEntity.getBody();
if (status != HttpStatus.OK.value()) {
GraceException.display(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
}
service-article com/imooc/article/service/ArticleService.java
/**
* 关联文章和gridfs的html文件id
*/
public void updateArticleToGridFS(String articleId, String articleMongoId);
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Transactional
@Override
public void updateArticleToGridFS(String articleId, String articleMongoId) {
Article pendingArticle = new Article();
pendingArticle.setId(articleId);
pendingArticle.setMongoFileId(articleMongoId);
articleMapper.updateByPrimaryKeySelective(pendingArticle);
}
【SwitchHosts】
# imooc-news 192.168.1.3
127.0.0.1 www.imoocnews.com
127.0.0.1 writer.imoocnews.com
127.0.0.1 admin.imoocnews.com
```
127.0.0.1 article.imoocnews.com
127.0.0.1 user.imoocnews.com
127.0.0.1 files.imoocnews.com
127.0.0.1 html.imoocnews.com
发布文章后审核文章
此时会发现数据库MongoDB里面的GridFS存储桶有新建的html 包括在 前端也存在此文件
D:\apache-tomcat-8.5.93\webapps\imooc-news\portal\a\240731CN3X1M56Y8.html
撤回删除文章,删除gridfs文件以及html【静态化解耦】
拿到mongodb_id 去删除 在service-html 写个删除接口 拼接删除方法
service-api com/imooc/api/controller/article/ArticleHTMLControllerApi.java
@Api(value = "静态化文章业务的controller", tags = {"静态化文章业务的controller"})
@RequestMapping("article/html")
public interface ArticleHTMLControllerApi {
@GetMapping("delete")
@ApiOperation(value = "删除html", notes = "删除html", httpMethod = "GET")
public Integer delete(String articleId) throws Exception;
}
article-html com/imooc/article/html/controller/ArticleHTMLController.java
@Override
public Integer delete(String articleId) throws Exception {
// 拼接最终文件的保存的地址
String path = articlePath + File.separator + articleId + ".html";
// 获取文件流,定义存放的位置和名称
File file = new File(path);
// 删除文件
file.delete();
return HttpStatus.OK.value();
}
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult delete(String userId, String articleId) {
articleService.deleteArticle(userId,articleId);
return GraceJSONResult.ok();
}
@Override
public GraceJSONResult withdraw(String userId, String articleId) {
articleService.withdrawArticle(userId, articleId);
return GraceJSONResult.ok();
}
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
@Transactional
@Override
public void deleteArticle(String userId, String articleId) {
Example articleExample = makeExampleCriteria(userId, articleId);
Article pending = new Article();
pending.setIsDelete(YesOrNo.YES.type);
int result = articleMapper.updateByExampleSelective(pending, articleExample);
if (result != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_DELETE_ERROR);
}
deleteHTML(articleId);
}
@Transactional
@Override
public void withdrawArticle(String userId, String articleId) {
Example articleExample = makeExampleCriteria(userId, articleId);
Article pending = new Article();
pending.setArticleStatus(ArticleReviewStatus.WITHDRAW.type);
int result = articleMapper.updateByExampleSelective(pending, articleExample);
if (result != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_WITHDRAW_ERROR);
}
deleteHTML(articleId);
}
...
...
@Autowired
private GridFSBucket gridFSBucket;
/**
* 文章撤回删除后,删除静态化的html
*/
public void deleteHTML(String articleId) {
// 1. 查询文章的mongoFileId
Article pending = articleMapper.selectByPrimaryKey(articleId);
String articleMongoId = pending.getMongoFileId();
// 2. 删除GridFS上的文件
gridFSBucket.delete(new ObjectId(articleMongoId));
// 3. 删除消费端的HTML文件
doDeleteArticleHTML(articleId);
// doDeleteArticleHTMLByMQ(articleId);
}
@Autowired
public RestTemplate restTemplate;
private void doDeleteArticleHTML(String articleId) {
String url = "http://html.imoocnews.com:8002/article/html/delete?articleId=" + articleId;
ResponseEntity<Integer> responseEntity = restTemplate.getForEntity(url, Integer.class);
int status = responseEntity.getBody();
if (status != HttpStatus.OK.value()) {
GraceException.display(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
}
}
接口解耦需求【章节概述】
- 介绍RabbitMQ
- RabbitMQ术语
- 安装与配置消息队列
- 实现接口调用解耦
RabbitMQ概述_MQ模型
消息队列
- RabbitMQ
- ActiveMQ
- RocketMQ
- Kafka
RabbitMQ
- erlang语言开发
- AMQP
- 应用之间通信
- https://www.rabbitmq.com/
应用场景
- 异步任务
- 提速
- 接口解耦
- 削峰
RabbitMQ模型
RabbitMQ-3.8.5 安装与配置详细在”多线程与分布式.md“中有
E:\Java实例项目1-20套\第04套【项目实战】Spring Cloud分布式微服务实战,打造大型自媒体3大业务平台 分布式前后端分离项目分层聚合 养成应对复杂业务的综合技术能力\imooc-news\rabbitmq-server-3.8.5
rabbitmq/erlang - Installation · packagecloud- Bash Scripts
[imooc@imooc ~]$ curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
[imooc@imooc ~]$ sudo yum install erlang
[imooc@imooc ~]$ erl
Erlang/OTP 23 [erts-11.2.2.10] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [hipe]
Eshell V11.2.2.10 (abort with ^G)
1>
[imooc@imooc ~]$ yum list | grep erlang
erlang.x86_64 23.3.4.11-1.el7 @rabbitmq_erlang
erlang-debuginfo.x86_64 23.3.4.11-1.el7 rabbitmq_erlang
[imooc@imooc ~]$ sudo rpm --import https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey
[imooc@imooc ~]$ sudo rpm --import https://packagecloud.io/gpg.key
将资源包里的文件拷贝过来 rabbitmq.conf 和 rabbitmq-server.rpm
#[先把两个依赖搞好 => 一个是key 一个是依赖]
[imooc@imooc ~]$ sudo rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
[imooc@imooc ~]$ sudo yum install socat
[imooc@imooc ~]$ sudo rpm -ivh rabbitmq-server-3.8.5-1.el7.noarch.rpm
[imooc@imooc ~]$ sudo vim rabbitmq.conf
#{loopback_users, []} 加上注释#
[imooc@imooc ~]$ cd /etc/rabbitmq/
#把conf移动到etc中
[imooc@imooc rabbitmq]$ sudo cp /home/imooc/rabbitmq.conf .
#重新启动rabbitmq
[imooc@imooc rabbitmq]$ sudo systemctl restart rabbitmq-server
#查看状态
[imooc@imooc rabbitmq]$ sudo systemctl status rabbitmq-server
● rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
Active: active (running) since 四 2024-08-01 15:45:54 CST; 13s ago
Main PID: 5843 (beam.smp)
Status: "Initialized"
Tasks: 86
CGroup: /system.slice/rabbitmq-server.service
├─5843 /usr/lib64/erlang/erts-11.2.2.10/bin/beam.smp -W w -K true -A 64 -M...
├─5952 erl_child_setup 32768
├─6008 inet_gethost 4
└─6009 inet_gethost 4
[imooc@imooc rabbitmq]$ sudo rabbitmq-plugins enable rabbitmq_management
[imooc@imooc rabbitmq]$ ll
总用量 40
-rw-r--r--. 1 root rabbitmq 23 8月 1 15:46 enabled_plugins
-rw-r--r--. 1 root rabbitmq 33325 8月 1 15:35 rabbitmq.conf
http://192.168.170.135:15672/
username: guest
password: guest
[imooc@imooc ~]$ sudo vim rabbitmq.conf #把这个取消注释
loopback_users.guest = false
http://192.168.170.135:15672/#/ #创建虚拟host节点
→ admin → Add a user
引入依赖和配置【集成Rabbitmq】
service-api pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
模块 Module
imooc-news-dev-service-article 是生产者[发送消息]
→
imooc-news-dev-service-article-html 是消费者[处理消息]
RabbitMQ Management 在Virtual Hosts → Add a new virtual host → Name: imooc-news-dev
退出再重新登陆一下rabbitmq → 账号密码:admin
service-article application.yml
rabbitmq:
host: 192.168.170.135
port: 5672
username: admin
password: admin
virtual-host: imooc-news-dev
service-article-html application.yml
rabbitmq:
host: 192.168.170.135
port: 5672
username: admin
password: admin
virtual-host: imooc-news-dev
创建交换机和队列【集成Rabbitmq】
service-api com/imooc/api/config/RabbitMQConfig.java
package com.imooc.api.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ 的配置类
*/
@Configuration
public class RabbitMQConfig {
// 定义交换机的名字
public static final String EXCHANGE_ARTICLE = "exchange_article";
// 定义队列的名字
public static final String QUEUE_DOWNLOAD_HTML = "queue_download_html";
// 创建交换机
@Bean(EXCHANGE_ARTICLE)
public Exchange exchange(){
return ExchangeBuilder
.topicExchange(EXCHANGE_ARTICLE)
.durable(true)
.build();
}
// 创建队列
@Bean(QUEUE_DOWNLOAD_HTML)
public Queue queue(){
return new Queue(QUEUE_DOWNLOAD_HTML);
}
// 队列绑定交换机
@Bean
public Binding binding(
@Qualifier(QUEUE_DOWNLOAD_HTML) Queue queue,
@Qualifier(EXCHANGE_ARTICLE) Exchange exchange){
return BindingBuilder
.bind(queue)
.to(exchange)
//.with("article.*") "article.hello", //类似于API的规则
.with("article.#.do")
.noargs(); // 执行绑定
}
}
创建生产者_配置路由规则【集成RabbitMQ】
{
“status”: 200,
“msg”: “操作成功!”,
“success”: true,
“data”: null
}
//如果队列规则改变 就需要把Exchanges里的RoutingKey解绑[Unbind] 否则还是会有以前的规则收到消息
//http://192.168.170.135:15672/#/exchanges/imooc-news-dev/exchange_article
package com.imooc.article.controller;
import com.imooc.api.config.RabbitMQConfig;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("producer")
public class HelloController{
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/hello")
public Object hello() {
/**
* RabbitMQ的路由规则 routing key
* display.*.* → * 代表一个占位符
* .with("article.#.do") //类似于API的规则
* 例:
* display.do.download 匹配
* display.do.upload.done 不匹配
*
* display.# → # 代表任意多个占位符
* 例:
* display.do.download 匹配
* display.do.upload.done.over 匹配
*/
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.publish.download.do", //要绑定规则
"1001~");
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.success.do", //要绑定规则
"1002~");
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.play", //要绑定规则
"1003~");
return GraceJSONResult.ok();
}
}
http://localhost:8001/producer/hello
消费者接受消息处理业务【集成RabbitMQ】
如果消息被消费掉后那么就 需要重新请求消息队列生成
service-article-html com/imooc/article/html/RabbitMQConsumer.java
package com.imooc.article.html;
import com.imooc.api.config.RabbitMQConfig;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
//打断点后 只要队列有消息 消费者监听到就会被消费
@Component
public class RabbitMQConsumer {
@RabbitListener(queues = {RabbitMQConfig.QUEUE_DOWNLOAD_HTML})//监听哪个队列
public void watchQueue(String payload, Message message){
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
if (routingKey.equalsIgnoreCase("article.publish.download.do")) {
System.out.println("article.publish.download.do");
} else if (routingKey.equalsIgnoreCase("article.success.do")) {
System.out.println("article.success.do");
} else {
System.out.println("不符合的规则:" + routingKey);
}
}
}
==================================================================
// 如果消息被消费掉后那么就 需要重新请求消息队列生成
// 此时需要刷新 http://localhost:8001/producer/hello 重新提交一下消息就可以了
Console:
1001~
article.publish.download.do
1002~
article.success.do
1003~
不符合的规则:article.play
文章静态化HTML与删除【异步解耦】
service-article com/imooc/article/controller/ArticleController.java
@Override
public GraceJSONResult doReview(String articleId, Integer passOrNot) {
Integer pendingStatus;
if (passOrNot == YesOrNo.YES.type) {
// 审核成功
pendingStatus = ArticleReviewStatus.SUCCESS.type;
} else if (passOrNot == YesOrNo.NO.type) {
// 审核失败
pendingStatus = ArticleReviewStatus.FAILED.type;
} else {
return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_REVIEW_ERROR);
}
// 保存到数据库,更改文章状态为审核成功或者失败
articleService.updateArticleStatus(articleId, pendingStatus);
if (pendingStatus == ArticleReviewStatus.SUCCESS.type){
//审核成功,生成文章详情页静态html
try{
// createArticleHTML(articleId);
String articleMongoId = createArticleHTMLToGridFS(articleId);
// 存储到对应的文章 进行关联保存
articleService.updateArticleToGridFS(articleId, articleMongoId);
// 调用消费端,执行下载html
// doDownloadArticleHTML(articleId,articleMongoId);
★★ // 发送消息到mq队列,让消费者监听并且下载html ★★
doDownloadArticleHTMLByMQ(articleId,articleMongoId);
}catch (Exception e){
e.printStackTrace();
}
}
return GraceJSONResult.ok();
}
@Autowired
private RabbitTemplate rabbitTemplate;
private void doDownloadArticleHTMLByMQ(String articleId, String articleMongoId) {
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_ARTICLE,
"article.download.do",
articleId + "," + articleMongoId);
}
service-article-html com/imooc/article/html/controller/ArticleHTMLComponent.java
package com.imooc.article.html.controller;
import com.mongodb.client.gridfs.GridFSBucket;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
@Component
public class ArticleHTMLComponent {
@Autowired
private GridFSBucket gridFSBucket;
@Value("${freemarker.html.article}")
private String articlePath;
public Integer download(String articleId, String articleMongoId)
throws Exception {
// 拼接最终文件的保存的地址
String path = articlePath + File.separator + articleId + ".html";
// 获取文件流,定义存放的位置和名称
File file = new File(path);
// 创建输出流
OutputStream outputStream = new FileOutputStream(file);
// 执行下载
gridFSBucket.downloadToStream(new ObjectId(articleMongoId), outputStream);
return HttpStatus.OK.value();
}
public Integer delete(String articleId) throws Exception {
// 拼接最终文件的保存的地址
String path = articlePath + File.separator + articleId + ".html";
// 获取文件流,定义存放的位置和名称
File file = new File(path);
// 删除文件
file.delete();
return HttpStatus.OK.value();
}
}
service-article-html com/imooc/article/html/RabbitMQConsumer.java
package com.imooc.article.html;
import com.imooc.api.config.RabbitMQConfig;
import com.imooc.article.html.controller.ArticleHTMLComponent;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
//打断点后 只要队列有消息 消费者监听到就会被消费
@Component
public class RabbitMQConsumer {
@Autowired
private ArticleHTMLComponent articleHTMLComponent;
@RabbitListener(queues = {RabbitMQConfig.QUEUE_DOWNLOAD_HTML})//监听哪个队列
public void watchQueue(String payload, Message message){
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
if (routingKey.equalsIgnoreCase("article.publish.download.do")) {
System.out.println("article.publish.download.do");
} else if (routingKey.equalsIgnoreCase("article.success.do")) {
System.out.println("article.success.do");
}else if (routingKey.equalsIgnoreCase("article.download.do")) {
String articleId = payload.split(",")[0];
String articleMongoId = payload.split(",")[1];
try {
articleHTMLComponent.download(articleId, articleMongoId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (routingKey.equalsIgnoreCase("article.html.download.do")) {
String articleId = payload;
try {
articleHTMLComponent.delete(articleId);
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("不符合的规则:" + routingKey);
}
}
}
前端的index.html页面也需要修改成静态页面跳转
<a :href="'./a/'+article.id+'.html'" target="_blank" class="link-article-title">{{article.title}}</a>
这样再次刷新 就可以让页面不是articleId=?...
http://www.imoocnews.com:9090/imooc-news/portal/a/240801D7S7PM63R4.html
延迟队列的需求与安装配置【延迟队列】
把这个rabbitmq_delayed_message_exchange-3.8.0.ez上传到Linux虚拟机
[imooc@imooc rabbitmq]$ cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.5/plugins
[imooc@imooc ~]$ sudo mv /home/imooc/rabbitmq_delayed_message_exchange-3.8.0.ez /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.5/plugins
[imooc@imooc plugins]$ sudo systemctl restart rabbitmq-server
[imooc@imooc plugins]$ sudo rabbitmq-plugins list
Listing plugins with pattern ".*" ...
Configured: E = explicitly enabled; e = implicitly enabled
| Status: * = running on rabbit@imooc
|/
[ ] rabbitmq_amqp1_0 3.8.5
[ ] rabbitmq_auth_backend_cache 3.8.5
[ ] rabbitmq_auth_backend_http 3.8.5
[ ] rabbitmq_auth_backend_ldap 3.8.5
[ ] rabbitmq_auth_backend_oauth2 3.8.5
[ ] rabbitmq_auth_mechanism_ssl 3.8.5
[ ] rabbitmq_consistent_hash_exchange 3.8.5
[ ] rabbitmq_delayed_message_exchange 3.8.0
[ ] rabbitmq_event_exchange 3.8.5
[ ] rabbitmq_federation 3.8.5
[ ] rabbitmq_federation_management 3.8.5
[ ] rabbitmq_jms_topic_exchange 3.8.5
[E*] rabbitmq_management 3.8.5
[e*] rabbitmq_management_agent 3.8.5
[ ] rabbitmq_mqtt 3.8.5
[ ] rabbitmq_peer_discovery_aws 3.8.5
[ ] rabbitmq_peer_discovery_common 3.8.5
[ ] rabbitmq_peer_discovery_consul 3.8.5
[ ] rabbitmq_peer_discovery_etcd 3.8.5
[ ] rabbitmq_peer_discovery_k8s 3.8.5
[ ] rabbitmq_prometheus 3.8.5
[ ] rabbitmq_random_exchange 3.8.5
[ ] rabbitmq_recent_history_exchange 3.8.5
[ ] rabbitmq_sharding 3.8.5
[ ] rabbitmq_shovel 3.8.5
[ ] rabbitmq_shovel_management 3.8.5
[ ] rabbitmq_stomp 3.8.5
[ ] rabbitmq_top 3.8.5
[ ] rabbitmq_tracing 3.8.5
[ ] rabbitmq_trust_store 3.8.5
[e*] rabbitmq_web_dispatch 3.8.5
[ ] rabbitmq_web_mqtt 3.8.5
[ ] rabbitmq_web_mqtt_examples 3.8.5
[ ] rabbitmq_web_stomp 3.8.5
[ ] rabbitmq_web_stomp_examples 3.8.5
[imooc@imooc plugins]$ service rabbitmq-server restart #[重启服务]
Redirecting to /bin/systemctl restart rabbitmq-server.service
实现延迟队列【延迟队列】
service-api com/imooc/api/config/RabbitMQDelayConfig.java
package com.imooc.api.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* RabbitMQ 的配置类
*/
@Configuration
public class RabbitMQDelayConfig {
// 定义交换机的名字
public static final String EXCHANGE_DELAY = "exchange_delay";
// 定义队列的名字
public static final String QUEUE_DELAY = "queue_delay";
// 创建延迟交换机
@Bean(EXCHANGE_DELAY)
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "topic");
return new CustomExchange(EXCHANGE_DELAY, "x-delayed-message", true, false, args);
}
// 创建队列
@Bean(QUEUE_DELAY)
public Queue queue(){
return new Queue(QUEUE_DELAY);
}
// 队列绑定交换机 ↓ binding必须要唯一
@Bean
public Binding delayBinding(
@Qualifier(QUEUE_DELAY) Queue queue,
@Qualifier(EXCHANGE_DELAY) Exchange exchange){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("delay.#")
.noargs(); // 执行绑定
}
}
service-article com/imooc/article/RabbitMQDelayConsumer.java
package com.imooc.article;
import com.imooc.api.config.RabbitMQDelayConfig;
import com.imooc.article.service.ArticleService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class RabbitMQDelayConsumer {
@Autowired
private ArticleService articleService;
@RabbitListener(queues = {RabbitMQDelayConfig.QUEUE_DELAY})
public void watchQueue(String payload, Message message) {
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
System.out.println(routingKey);
System.out.println("消费者接受的延迟消息:" + new Date());
// // 消费者接收到定时发布的延迟消息,修改当前的文章状态为`即时发布`
// String articleId = payload;
// articleService.updateArticleToPublish(articleId);
}
}
service-article com/imooc/article/controller/HelloController.java
package com.imooc.article.controller;
import com.imooc.api.config.RabbitMQConfig;
import com.imooc.api.config.RabbitMQDelayConfig;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequestMapping("producer")
public class HelloController{
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/hello")
public Object hello() {
/**
* RabbitMQ的路由规则 routing key
* display.*.* → * 代表一个占位符
* .with("article.#.do") //类似于API的规则
* 例:
* display.do.download 匹配
* display.do.upload.done 不匹配
*
* display.# → # 代表任意多个占位符
* 例:
* display.do.download 匹配
* display.do.upload.done.over 匹配
*/
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.publish.download.do", //要绑定规则
"1001~");
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.success.do", //要绑定规则
"1002~");
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.play", //要绑定规则
"1003~");
return GraceJSONResult.ok();
}
@GetMapping("/delay")
public Object delay() {
//重写延迟方法 【生产者】
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 设置消息的持久
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 设置消息延迟的时间 单位ms毫秒
message.getMessageProperties().setDelay(5000);
return message;
}
};
rabbitTemplate.convertAndSend(RabbitMQDelayConfig.EXCHANGE_DELAY,
"delay.demo", //要绑定规则
"这是一条延时消息~",
messagePostProcessor);
System.out.println("生产者发送的延迟消息:" + new Date());
return "OK";
}
}
确保 rabbitmq_delayed_message_exchange 插件正确启用:
复制代码
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
sudo systemctl restart rabbitmq-server
======================================================================
http://localhost:8001/producer/delay
生产者发送的延迟消息:Thu Aug 01 20:17:30 CST 2024
这是一条延时消息~
delay.demo
消费者接受的延迟消息:Thu Aug 01 20:17:35 CST 2024
实现文章的定时延时发布【延迟队列】
service-article com/imooc/article/service/ArticleService.java
/**
* 更新定时发布为即使发布
public void updateAppointToPublish(); **/
/**
* 更新单条文章为记时发布
*/
public void updateArticleToPublish(String articleId);
service-article com/imooc/article/service/impl/ArticleServiceImpl.java
【32-64行 89-101行】
@Transactional
@Override
public void createArticle(NewArticleBO newArticleBO, Category category) {
String articleId = sid.nextShort();
Article article = new Article();
BeanUtils.copyProperties(newArticleBO, article);
article.setId(articleId);
article.setCategoryId(category.getId());
article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
article.setCommentCounts(0);
article.setReadCounts(0);
article.setIsDelete(YesOrNo.NO.type);
article.setCreateTime(new Date());
article.setUpdateTime(new Date());
if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
article.setPublishTime(newArticleBO.getPublishTime()); //用户可以在前端选择定时发布
} else if (article.getIsAppoint() == ArticleAppointType.IMMEDIATELY.type) {
article.setPublishTime(new Date());
}
int res = articleMapper.insert(article);
if (res != 1) {
GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
}
// 发送延迟消息到mq,计算定时发布时间和当前时间的时间差,则为往后延迟的时间
if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
Date endDate = newArticleBO.getPublishTime();
Date startDate = new Date();
int delayTimes = (int)(endDate.getTime() - startDate.getTime());
System.out.println(DateUtil.timeBetween(startDate, endDate));
// FIXME: 为了测试方便,写死10s
// int delayTimes = 10 * 1000;
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 设置消息的持久
message.getMessageProperties()
.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 设置消息延迟的时间,单位ms毫秒
message.getMessageProperties()
.setDelay(delayTimes);
return message;
}
};
rabbitTemplate.convertAndSend(
RabbitMQDelayConfig.EXCHANGE_DELAY,
"publish.delay.display",
articleId,
messagePostProcessor);
System.out.println("延迟消息-定时发布文章:" + new Date());
}
/**
* FIXME: 我们只检测正常的词汇,非正常词汇大家课后去检测
*/
// 通过阿里智能AI实现对文章文本的自动检测(自动审核)
// String reviewTextResult = aliTextReviewUtils.reviewTextContent(newArticleBO.getContent());
String reviewTextResult = ArticleReviewLevel.REVIEW.type;
if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.PASS.type)) {
// 修改当前的文章,状态标记为审核通过
this.updateArticleStatus(articleId, ArticleReviewStatus.SUCCESS.type);
} else if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.REVIEW.type)) {
// 修改当前的文章,状态标记为需要人工审核
this.updateArticleStatus(articleId, ArticleReviewStatus.WAITING_MANUAL.type);
} else if (reviewTextResult
.equalsIgnoreCase(ArticleReviewLevel.BLOCK.type)) {
// 修改当前的文章,状态标记为审核未通过
this.updateArticleStatus(articleId, ArticleReviewStatus.FAILED.type);
}
}
@Transactional //添加事务[更新操作]
/** @Override
public void updateAppointToPublish() {
articleMapperCustom.updateAppointToPublish();
} **/
@Override
public void updateArticleToPublish(String articleId) {
Article article = new Article();
article.setId(articleId);
article.setIsAppoint(ArticleAppointType.IMMEDIATELY.type);
articleMapper.updateByPrimaryKeySelective(article);
}
service-article com/imooc/article/RabbitMQDelayConsumer.java
package com.imooc.article;
import com.imooc.api.config.RabbitMQDelayConfig;
import com.imooc.article.service.ArticleService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class RabbitMQDelayConsumer {
@Autowired
private ArticleService articleService;
@RabbitListener(queues = {RabbitMQDelayConfig.QUEUE_DELAY})
public void watchQueue(String payload, Message message) {
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
System.out.println(routingKey);
System.out.println("消费者接受的延迟消息:" + new Date());
// 消费者接收到定时发布的延迟消息,修改当前的文章状态为`即时发布`
String articleId = payload;
articleService.updateArticleToPublish(articleId);
}
}
service-api com/imooc/api/config/RabbitMQDelayConfig.java //【换一下绑定类型.with(...)】
package com.imooc.api.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* RabbitMQ 的配置类
*/
@Configuration
public class RabbitMQDelayConfig {
// 定义交换机的名字
public static final String EXCHANGE_DELAY = "exchange_delay";
// 定义队列的名字
public static final String QUEUE_DELAY = "queue_delay";
// 创建延迟交换机
@Bean(EXCHANGE_DELAY)
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "topic");
return new CustomExchange(EXCHANGE_DELAY, "x-delayed-message", true, false, args);
}
// 创建队列
@Bean(QUEUE_DELAY)
public Queue queue(){
return new Queue(QUEUE_DELAY);
}
// 队列绑定交换机 ↓ binding必须要唯一
@Bean
public Binding delayBinding(
@Qualifier(QUEUE_DELAY) Queue queue,
@Qualifier(EXCHANGE_DELAY) Exchange exchange){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("publish.delay.#")
.noargs(); // 执行绑定
}
}
http://writer.imoocnews.com:9090/imooc-news/writer/createArticle.html 发布一篇定时文章
// 在数据库里面是article → is_appoint 是1 然后延迟3天后会变成0
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15650472]
JDBC Connection [HikariProxyConnection@441638108 wrapping org.mariadb.jdbc.MariaDbConnection@159b2e33] will be managed by Spring
==> Preparing: INSERT INTO article ( id,title,category_id,article_type,article_cover,is_appoint,article_status,publish_user_id,publish_time,read_counts,comment_counts,mongo_file_id,is_delete,create_time,update_time,content ) VALUES( ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? )
==> Parameters: 240801FNS7M8G354(String), b10(String), 14(Integer), 2(Integer), (String), 1(Integer), 1(Integer), 240629F21AK1BHX4(String), 2024-08-04 00:00:00.0(Timestamp), 0(Integer), 0(Integer), null, 0(Integer), 2024-08-01 20:36:24.971(Timestamp), 2024-08-01 20:36:24.971(Timestamp), <p>b10</p>(String)
//★ <== Updates: 1 ★
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15650472]
//★ 2天3小时23分钟 ★
//★ 延迟消息-定时发布文章:Thu Aug 01 20:36:24 CST 2024 ★
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@15650472] from current transaction
==> Preparing: UPDATE article SET article_status = ? WHERE ( ( id = ? ) )
==> Parameters: 2(Integer), 240801FNS7M8G354(String)
<== Updates: 1
互联网框架演变【微服务块】
- 架构演变
- 微服务入门
- SpringCloud各个组件学习
- 改造项目服务化
注冊中心模型
- Eureka
- 可以把每個服務注入到eureka,更利於管理和維護,使得服務閒通信更方便
Lilei [上户口] → 派出所 ← [上户口] HanMeimei
构建Eureka注册服务【eureka】
springcloud-eureka com/imooc/eureka/Application.java
package com.imooc.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MongoAutoConfiguration.class})
@EnableEurekaServer // 开启注册中心
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
http://localhost:7000/hello #运行接口
http://localhost:7000 #运行eureka
springcloud-eureka com/imooc/eureka/controller/HelloController.java
package com.imooc.eureka.controller;
import com.imooc.api.controller.user.HelloControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.RedisOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloControllerApi {
final static Logger logger = LoggerFactory.getLogger(HelloController.class);
public Object hello(){
return GraceJSONResult.ok();
}
}
springcloud-eureka 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">
<parent>
<artifactId>imooc-news-dev</artifactId>
<groupId>com.imooc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 其他必要的依赖 -->
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
springcloud-eureka logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<!-- <property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/service-admin"/>-->
<property name="LOG_HOME" value="C:/Users/Pluminary/Desktop/imooc-news-dev/springcloud-eureka"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/eureka.%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>
<!--<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--</logger>-->
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
springcloud-eureka application.yml
############################################################
#
# eureka 注册中心
# web访问端口号 约定:7000
#
############################################################
server:
port: 7000
tomcat:
uri-encoding: UTF-8
max-swallow-size: -1 # tomcat默认大小2M,超过2M的文件不会被捕获,需要调整此处大小为100MB或者-1即可
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-eureka
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance: # eureka 实例的hostname,也可以是自定义配置hostname
hostname: eureka
client: # 是否要把当前的eureka server注册到自己
register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
http://localhost:7000/
进入了Spring Eureka
Instances currently registered with Eureka
实现用户与文章的服务注册【eureka】
关于eureka客户端启动报错UnknownHostException详细解决方法_eureka unknownhostexception-CSDN博客
# 问题:
http://eureka:7000/eureka 找不到 eureka 的服务器 IP 地址
在cmd里面
C:\Users\Pluminary>ping eureka
Ping 请求找不到主机 eureka。请检查该名称,然后重试。
# 解答:
从你的描述来看,主机eureka无法解析,这是导致服务无法注册到Eureka Server的原因。你可以通过以下方法解决这个问题:
1. 更新 hosts 文件
在你的开发机器上更新 hosts 文件以手动解析 eureka 主机名。
Windows
打开记事本以管理员身份运行。
打开文件 C:\Windows\System32\drivers\etc\hosts。
添加以下行,将 <eureka服务器的IP地址> 替换为实际的IP地址:
plaintext
复制代码
<eureka服务器的IP地址> eureka
保存并关闭文件。
=================================================================
# imooc-news 192.168.1.3
127.0.0.1 www.imoocnews.com
127.0.0.1 writer.imoocnews.com
127.0.0.1 admin.imoocnews.com
```
127.0.0.1 article.imoocnews.com
127.0.0.1 user.imoocnews.com
127.0.0.1 files.imoocnews.com
127.0.0.1 html.imoocnews.com
springcloud-eureka resources/application.yml
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance:
# eureka 实例的hostname,可以是hostname,也可以自定义配置hostname
hostname: eureka
client:
# 是否要把当前的eureka server注册到自己
register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
service-user resources/application.yml
上面的其余不变 底下添加eureka
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
service-user com/imooc/user/Application.java
package com.imooc.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.user.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient // 开启eureka client 注册到server中
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
springcloud-eureka 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">
<parent>
<artifactId>imooc-news-dev</artifactId>
<groupId>com.imooc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
springcloud-eureka com/imooc/user/Application.java
package com.imooc.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, MongoAutoConfiguration.class})
@EnableEurekaServer // 开启注册中心
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
====================================================================
// 先启动这个服务 再启动user的服务
随后就能看见http://localhost:7000/
里面有一个注册的服务
/*
Instances currently registered with Eureka
Application AMIs Availability Zones Status
★ SERVICE-USER n/a (1) (1) UP (1) - localhost:service-user:8003
*/
service-article resources/application.yml
# 定义freemarker生成的HTML
freemarker:
html:
target: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
article: D:/apache-tomcat-8.5.93/webapps/imooc-news/portal/a
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
service-article com/imooc/article/Application.java
//【此时再去启动这个article服务 会发现SERVICE-ARTICLE也成功的注册到Eureka中】
package com.imooc.article;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
/*
Application AMIs Availability Zones Status
★ SERVICE-ARTICLE n/a (1) (1) UP (1) - localhost:service-article:8001
★ SERVICE-USER n/a (1) (1) UP (1) - localhost:service-user:8003
*/
使用AppName优化服务间的通信【eureka】
实行动态化调用 地址拼接
AppName是eureka的ApplicationId = SERVICE-USER
慕课新闻·[文章article]自媒体接口api 如果页面没有信息那就是在Swagger2.java中代码的问题
Eureka
门户端文章业务的controller → /portal/article/detail → articleId=2006117B57WRZGHH
package com.imooc.api.config;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration //Springboot啓動的時候會被掃描到并且加載
@EnableSwagger2
public class Swagger2 {
// http://localhost:8088/swagger-ui.html 原路径
// http://localhost:8088/doc.html 新路径
// 配置swagger2核心配置 docket
@Bean
public Docket createRestApi() {
Predicate<RequestHandler> adminPredicate = RequestHandlerSelectors.basePackage("com.imooc.admin.controller");
Predicate<RequestHandler> articlePredicate = RequestHandlerSelectors.basePackage("com.imooc.article.controller");
Predicate<RequestHandler> userPredicate = RequestHandlerSelectors.basePackage("com.imooc.user.controller");
Predicate<RequestHandler> filesPredicate = RequestHandlerSelectors.basePackage("com.imooc.files.controller");
return new Docket(DocumentationType.SWAGGER_2) // 指定api类型为swagger2
.apiInfo(apiInfo()) // 用于定义api文档汇总信息
.select()
// .apis(Predicates.or(userPredicate, adminPredicate, filesPredicate))
.apis(Predicates.or(adminPredicate, articlePredicate, userPredicate, filesPredicate))
.paths(PathSelectors.any()) // 所有controller
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("慕课新闻·自媒体接口api") // 文档页标题
.contact(new Contact("imooc",
"https://www.imooc.com",
"abc@imooc.com")) // 联系人信息
.description("专为慕课新闻·自媒体平台提供的api文档") // 详细信息
.version("1.0.1") // 文档版本号
.termsOfServiceUrl("https://www.imooc.com") // 网站地址
.build();
}
}
service-article com/imooc/article/controller/ArticlePortalController.java
// 注入服务发现,可以获得已经注册的服务相关信息
@Autowired
private DiscoveryClient discoveryClient;
// 发起远程调用,获得用户的基本信息
private List<AppUserVO> getPublisherList(Set idSet) {
String serviceId = "SERVICE-USER";
List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
ServiceInstance userService = instanceList.get(0);
// 实行动态化调用 地址拼接
String userServerUrlExecute
= "http://"+ userService.getHost() + ":" + userService.getPort() + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// String userServerUrlExecute
// = "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return publisherList;
}
动态构建eureka集群【eureka】保证高可用
创建一个新的module 其内容和 springcloud-eureka 里面的一样
SwitchHosts配置信息
# imooc-news 192.168.1.3
127.0.0.1 www.imoocnews.com
127.0.0.1 writer.imoocnews.com
127.0.0.1 admin.imoocnews.com
```
127.0.0.1 article.imoocnews.com
127.0.0.1 user.imoocnews.com
127.0.0.1 files.imoocnews.com
127.0.0.1 html.imoocnews.com
# SpringCloud
127.0.0.1 eureka
127.0.0.1 eureka-cluster-7001
127.0.0.1 eureka-cluster-7002
127.0.0.1 eureka-cluster-7003
springcloud-eureka-cluster application.yml
############################################################
#
# eureka 集群的注册中心
# web访问端口号 约定:7001~7003
#
############################################################
server:
port: 7001
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-eureka-cluster
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance:
# 集群中每个eureka的名字都是唯一的
hostname: eureka-cluster-7001
client:
# 是否要把当前的eureka server注册到自己
register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
####################################################################################
http://localhost:7001/
DS Replicas
eureka-cluster-7003
eureka-cluster-7002
####################################################################################
如果后面服务很多 100个 那是不是也要创建100个module呢?
并不是 因为每个都是一样的只是改一下application.yml的port端口号而已
所以我们要去把它设置为动态的端口
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
springcloud-eureka-cluster application.yml【改后】
############################################################
#
# eureka 集群的注册中心
# web访问端口号 约定:7001~7003
#
############################################################
server:
port: ${7001}
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-eureka-cluster
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance:
# 集群中每个eureka的名字都是唯一的
hostname: eureka-cluster-${server.port}
# 自定义端口号
other-node-port2: ${p2:7002}
other-node-port3: ${p3:7003}
client:
# 是否要把当前的eureka server注册到自己
register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://eureka-cluster-${eureka.other-node-port2}:${eureka.other-node-port2}/eureka/,http://eureka-cluster-${eureka.other-node-port3}:${eureka.other-node-port3}/eureka/
####################################################################################
http://eureka-cluster-7001:7001/ #【可运行】
1一站式管理所有SpringBoot启动类,Services服务窗口 - 喵酱张-Eric - 博客园 (cnblogs.com)
IDEA 2021没有VM options_idea2021怎么找到“vm options”-CSDN博客
复制eureka-cluster-7001服务 变成eureka-cluster-7002 并且在Edit configuration的地方点击Modify options中的Add VM 输入代码:**-DPORT=7002 -DP2=7001 -DP3=7003**
同理弄一个eureka-cluster-7003 输入VM代码:**-DPORT=7003 -DP2=7001 -DP3=7002**
全部启动后都可以在浏览器正常运行 【集群】
http://eureka-cluster-7001:7001/ DS Replicas:eureka-cluster-7003 + eureka-cluster-7002
http://eureka-cluster-7002:7002/ DS Replicas:eureka-cluster-7003 + eureka-cluster-7001
http://eureka-cluster-7003:7003/ DS Replicas:eureka-cluster-7002 + eureka-cluster-7001
如果把里面的application.yml配置注释掉 就可以把自己注册到eureka中
client:
# *是否要把当前的eureka server注册到自己
* register-with-eureka: false
# *从注册中心获得检索服务实例,server没有必要,直接false即可
* fetch-registry: false
之后再重新启动eureka-cluster-7001~7003DS Replicas
Instances currently registered with Eureka
Application AMIs Availability Zones Status SPRINGCLOUD-EUREKA-CLUSTER n/a (3) (3) UP (3) - localhost:springcloud-eureka-cluster:7003 , localhost:springcloud-eureka-cluster:7001 , localhost:springcloud-eureka-cluster:7002
微服务注册到eureka集群【eureka】${port:8003}
service-user application.yml
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/ 三个节点的注册
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
service-article application.yml
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
慕课新闻·自媒体接口:8001_api
门户端文章业务的controller → get:/portal/article/detail → articleId:2006117B57WRZGHH
DS Replicas
Instances currently registered with Eureka
Application | AMIs | Availability Zones | Status |
---|---|---|---|
SERVICE-ARTICLE | n/a (1) | (1) | UP (1) - localhost:service-article:8001 |
SERVICE-USER | n/a (1) | (1) | UP (1) - localhost:service-user:8003 |
SPRINGCLOUD-EUREKA-CLUSTER | n/a (3) | (3) | UP (3) - localhost:springcloud-eureka-cluster:7003 , localhost:springcloud-eureka-cluster:7001 , localhost:springcloud-eureka-cluster:7002 |
构建微服务集集群【eureka】
复制service-user:8003服务 变成service-user:8013 并且在Edit configuration的地方点击Modify options中的Add VM 输入代码:**–DPORT=8013**
service-user application-dev.yml
server:
port: ${port:8003}
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
# open mybatis log in dev
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# setup CN from java, This is resource
website:
domain-name: imoocnews.com
DS Replicas
Instances currently registered with Eureka
Application | AMIs | Availability Zones | Status |
---|---|---|---|
SERVICE-ARTICLE | n/a (1) | (1) | UP (1) - localhost:service-article:8001 |
SERVICE-USER | n/a (2) | (2) | UP (2) - localhost:service-user:8003 , localhost:service-user:8013 |
SPRINGCLOUD-EUREKA-CLUSTER | n/a (3) | (3) | UP (3) - localhost:springcloud-eureka-cluster:7003 , localhost:springcloud-eureka-cluster:7001 , localhost:springcloud-eureka-cluster:7002 |
实现轮训负载均衡【eureka】
imooc-news-dev-service-user application-dev.yml
server:
port: ${port:8003}
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
# open mybatis log in dev
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# setup CN from java, This is resource
website:
domain-name: imoocnews.com
service-user com/imooc/user/controller/UserController.java
//每次调用的时候 都会输出其Port
@Value("${server.port}")
private String myPort;
@Override
public GraceJSONResult queryByIds(String userIds) {
System.out.println("myPort=" + myPort);
if (StringUtils.isBlank(userIds)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
for (String userId : userIdList){
//获得用户基本信息
AppUserVO userVO = getBasicUserInfo(userId);
// 3.添加到publisherList
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
service-article com/imooc/article/controller/ArticlePortalController.java
// 注入服务发现,可以获得已经注册的服务相关信息
@Autowired
private DiscoveryClient discoveryClient;
// 发起远程调用,获得用户的基本信息
private List<AppUserVO> getPublisherList(Set idSet) {
String serviceId = "SERVICE-USER";
// List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
// ServiceInstance userService = instanceList.get(0);
// 实行动态化调用 地址拼接
String userServerUrlExecute
//因为seviceId里面的SERVICE—USER就存在着userService.getHost()和.getPort()
= "http://"+ serviceId + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// String userServerUrlExecute
// = "http://"+ userService.getHost() + ":" + userService.getPort() + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// String userServerUrlExecute
// = "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// 为restTemplate增加一个负载均衡 @CloudConfig
// public RestTemplate restTemplate()
ResponseEntity<GraceJSONResult> responseEntity
= restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return publisherList;
}
service-api com/imooc/api/config/CloudConfig.java
package com.imooc.api.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CloudConfig {
public CloudConfig() {
}
/**
* 会基于OKHttp3的配置来实例RestTemplate
* @return
*/
@Bean
@LoadBalanced //添加负载均衡
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
慕课新闻·自媒体接口:8001-api
门户端文章业务的controller → articleId:2006117B57WRZGHH
自我保护功能【eureka】
springcloud-eureka-cluster application.yml
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance:
# 集群中每个eureka的名字都是唯一的
hostname: eureka-cluster-${server.port}
# 自定义端口号
other-node-port2: ${p2:7002}
other-node-port3: ${p3:7003}
client:
# 是否要把当前的eureka server注册到自己
# register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
# fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://eureka-cluster-${eureka.other-node-port2}:${eureka.other-node-port2}/eureka/,http://eureka-cluster-${eureka.other-node-port3}:${eureka.other-node-port3}/eureka/
server:
enable-self-preservation: false # 关闭eureka的自我保护功能
eviction-interval-timer-in-ms: 5000 # 清理无效节点的时间,可以缩短为5s 默认60s
springcloud-eureka application.yml
############################################################
#
# eureka 配置信息
#
############################################################
eureka:
instance:
# eureka 实例的hostname,可以是hostname,也可以自定义配置hostname
hostname: eureka
client:
# 是否要把当前的eureka server注册到自己
register-with-eureka: false
# 从注册中心获得检索服务实例,server没有必要,直接false即可
fetch-registry: false
# 单实例配置自己的服务地址,高可用集群则配置多个地址
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false # 关闭eureka的自我保护功能
eviction-interval-timer-in-ms: 5000 # 清理无效节点的时间,可以缩短为5s 默认60s
service-user application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/ 三个节点的注册
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
service-article application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
instance:
# 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
lease-renewal-interval-in-seconds: 3
# eureka 距离最近的一次心跳等待提出的时间 默认90s
lease-expiration-duration-in-seconds: 5
Eureka:7001
先把所有服务全启动
eureka:7000
eureka-cluster-7001
eureka-cluster-7002
eureka-cluster-7003
service-article:8001
service-user:8003
service-user:8013
然后去刷新Instances currently registered with Eureka
Application AMIs Availability Zones Status SERVICE-ARTICLE n/a (1) (1) UP (1) - localhost:service-article:8001 SERVICE-USER n/a (2) (2) UP (2) - localhost:service-user:8003 , localhost:service-user:8013 SPRINGCLOUD-EUREKA-CLUSTER n/a (3) (3) UP (3) - localhost:springcloud-eureka-cluster:7003 , localhost:springcloud-eureka-cluster:7001 , localhost:springcloud-eureka-cluster:7002 随后只留下eureka-cluster-7001其他全部Stop
再次刷新Instances currently registered with Eureka
Application AMIs Availability Zones Status SPRINGCLOUD-EUREKA-CLUSTER n/a (1) (1) UP (1) - localhost:springcloud-eureka-cluster:7001
负载均衡工具
- Ribbon[本地] = RestTemplate + @LoadBalanced
- 服务间通信的负载均衡工具,提供完善的超时重试机制
service-api com/imooc/api/config/CloudConfig.java
package com.imooc.api.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CloudConfig {
public CloudConfig() {
}
/**
* 会基于OKHttp3的配置来实例RestTemplate
* @return
*/
@Bean
@LoadBalanced //添加负载均衡 默认的负载均衡算法:枚举
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
实现多种负载均衡算法【ribbon】
自定义规则
service-api com/imooc/api/config/CloudConfig.java
package com.imooc.api.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CloudConfig {
public CloudConfig() {
}
/**
* 会基于OKHttp3的配置来实例RestTemplate
* @return
*/
@Bean
@LoadBalanced //添加负载均衡 默认的负载均衡算法:枚举
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
service-api com/rule/MyRule.java
package com.rule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 官方定义了规则不要被 @ComponentScan( 扫描到
@Configuration
public class MyRule {
@Bean
public IRule iRule(){// 随机的负载均衡策略
return new RandomRule();
// 在调用方article的启动类开启注解 @RibbonClient
}
}
service-article com/imooc/article/Application.java
package com.imooc.article;
import com.rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient
@RibbonClient(name = "service-user", configuration = MyRule.class) //微服务名称
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
运用配置文件进行配置
慕课新闻·自媒体接口api 发送11次请求 门户端文章业务→articleId:2006117B57WRZGHH
service-user:8003请求到myPort=8003 8次
service-user:8013请求到myPort=8013 3次
service-article resources/application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
instance:
# 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
lease-renewal-interval-in-seconds: 3
# eureka 距离最近的一次心跳等待提出的时间 默认90s
lease-expiration-duration-in-seconds: 5
# 配置指定自定义的ribbon规则
SERVICE-USER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
service-article com/imooc/article/Application.java
package com.imooc.article;
import com.rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient
//@RibbonClient(name = "service-user", configuration = MyRule.class) //微服务名称
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
重试机制【ribbon】
节点有可能因为网络问题访问不到 而为了不让其返回错误 需要重试机制
sevice-api pom.xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
service-article application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
instance:
# 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
lease-renewal-interval-in-seconds: 3
# eureka 距离最近的一次心跳等待提出的时间 默认90s
lease-expiration-duration-in-seconds: 5
# 配置指定自定义的ribbon规则
SERVICE-USER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon:
ConnectTimeout: 5000 # 创建连接的超时时间,单位:ms
ReadTimeout: 5000 # 在连接创建好以后,调用接口的超时时间,单位:ms
MaxAutoRetries: 1 # 最大重试次数
MaxAutoRetriesNextServer: 2 # 切换到下个微服务实例的重试次数
# 当请求到某个微服务5s,超时后会进行重试,先重试连接自己当前的这个实例
# 如果当前重试失败1次,则会切换到访问集群中的下一个微服务实例,切换最大为2次
logging:
level:
# com.imooc.api.controller.user.UserControllerApi: debug
root: debug
慕课新闻·自媒体接口-8001api articleId:2006117B57WRZGHH
先把所有服务全部启动 然后把service-user:8013 的服务Stop
再去api接口发送请求 查看servcice-article:8001的Console输出日志
14:10.288 [http-nio-8001-exec-1] DEBUG o.s.retry.support.RetryTemplate - Retry: count=0
14:10.774 [http-nio-8001-exec-1] DEBUG o.s.web.client.RestTemplate - Response 200 OK
14:10.288 [http-nio-8001-exec-1] DEBUG o.s.retry.support.RetryTemplate - Retry: count=1
14:10.288 [http-nio-8001-exec-1] DEBUG o.s.retry.support.RetryTemplate - Retry: count=2
14:10.774 [http-nio-8001-exec-1] DEBUG o.s.web.client.RestTemplate - Response 200 OK
简化服务调用【feign】以Api作为接口,面向接口的编程风格
声明式HTTP工具
- Feign
- 声明式的http工具,用于简化服务调用
service-api pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
service-article com/imooc/article/controller/ArticlePortalController.java
// 注入服务发现,可以获得已经注册的服务相关信息
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private UserControllerApi userControllerApi;
//面向接口 UserControllerApi
// 发起远程调用,获得用户的基本信息
private List<AppUserVO> getPublisherList(Set idSet) {
// String serviceId = "SERVICE-USER";
// List<ServiceInstance> instanceList = discoveryClient.getInstances(serviceId);
// ServiceInstance userService = instanceList.get(0);
// 实行动态化调用 地址拼接
// String userServerUrlExecute
//因为seviceId里面的SERVICE—USER就存在着userService.getHost()和.getPort()
// = "http://"+ serviceId + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
GraceJSONResult bodyResult = userControllerApi.queryByIds(JsonUtils.objectToJson(idSet));
// String userServerUrlExecute
// = "http://"+ userService.getHost() + ":" + userService.getPort() + "/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// String userServerUrlExecute
// = "http://user.imoocnews.com:8003/user/queryByIds?userIds=" + JsonUtils.objectToJson(idSet);
// 为restTemplate增加一个负载均衡@CloudConfig public RestTemplate restTemplate()
// ResponseEntity<GraceJSONResult> responseEntity
// = restTemplate.getForEntity(userServerUrlExecute, GraceJSONResult.class);
// GraceJSONResult bodyResult = responseEntity.getBody();
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}
return publisherList;
}
service-article com/imooc/article/Application.java //【EnableFeignClients】
package com.imooc.article;
import com.rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient
//@RibbonClient(name = "service-user", configuration = MyRule.class) //微服务名称
@EnableFeignClients({"com.imooc"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
service-api com/imooc/api/config/MyServiceList.java
package com.imooc.api.config;
public class MyServiceList {
public static final String SERVICE_USER = "service-user";
}
service-article com/imooc/api/controller/user/UserControllerApi.java【@FeignClient】
package com.imooc.api.controller.user;
import com.imooc.api.config.MyServiceList;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user")
@FeignClient(value = MyServiceList.SERVICE_USER) //作为客户端直接调用
public interface UserControllerApi {
@ApiOperation(value = "获得用户基本信息",notes = "获得用户基本信息",httpMethod = "POST")
@PostMapping("/getUserInfo")
public GraceJSONResult getUserInfo(@RequestParam String userId);
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
@RequestParam BindingResult result); //RequestParam BindingResult result 加了肯定在前端不可用 对Feign而言不能存在两个对象不然会认为有两个model
@ApiOperation(value = "根据用户的ids查询用户列表",notes = "根据用户的ids查询用户列表",httpMethod = "GET")
@GetMapping("/queryByIds")
public GraceJSONResult queryByIds(@RequestParam String userIds);
}
统一检验处理【feign】
把所有的BindingResult都可以采用全局调用的方法去调用
慕课新闻·自媒体接口8003api → 用户信息相关 → 修改/完善用户信息 Post /user/updateUserInfo
dev-common com/imooc/exception/GraceExceptionHandler.java
package com.imooc.exception;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.naming.Binding;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 统一异常拦截处理
* 可以针对异常的类型进行捕获 然后返回json信息到前端
*/
@ControllerAdvice
public class GraceExceptionHandler {
/* @ExceptionHandler(MyCustomException.class)
//只要是这个类的异常都会进入下面的方法
@ResponseBody
public GraceJSONResult returnMyException(MyCustomException e){
e.printStackTrace(); //打印信息
return GraceJSONResult.exception(e.getResponseStatusEnum());
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseBody
public GraceJSONResult returnMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.FILE_MAX_SIZE_ERROR);
}*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody //该异常是基于所有的vo验证
public GraceJSONResult returnException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
Map<String, String> map = getErrors(result);
return GraceJSONResult.errorMap(map);
}
public Map<String, String> getErrors(BindingResult result) {
Map<String, String> map = new HashMap<>();
List<FieldError> errorList = result.getFieldErrors();
for (FieldError error : errorList) {
// 发送验证错误的时候所对应的某个属性
String field = error.getField();
// 验证的错误消息
String msg = error.getDefaultMessage();
map.put(field, msg);
}
return map;
}
}
service-api com/imooc/api/controller/user/UserControllerApi.java
// @ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
// @PostMapping("/updateUserInfo")
// public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
// @RequestParam BindingResult result);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO);
service-user com/imooc/user/controller/UserController.java
@Override
public GraceJSONResult updateUserInfo(@Valid UpdateUserInfoBO updateUserInfoBO){
//, BindingResult result) {
// // 0.校验BO
// if (result.hasErrors()){
// Map<String, String> map = getErrors(result);
// return GraceJSONResult.errorMap(map);
// }
// 1.执行更新操作
userService.updateUserInfo(updateUserInfoBO);
return GraceJSONResult.ok();
//调用UserService把独有信息传入
}
开启日志调式【feign】基于http的调用
service-article application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
# 自定义eureka server的信息
server:
hostname: eureka
port: 7000
client:
# 所有的微服务都必须注册到eureka中
register-with-eureka: true
# 从注册中心获得检索服务实例
fetch-registry: true
# 注册中心的服务地址
service-url:
# defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
instance:
# 调整微服务(eureka client)和注册中心(eureka server)的心跳时间
lease-renewal-interval-in-seconds: 3
# eureka 距离最近的一次心跳等待提出的时间 默认90s
lease-expiration-duration-in-seconds: 5
# 配置指定自定义的ribbon规则
SERVICE-USER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon:
ConnectTimeout: 5000 # 创建连接的超时时间,单位:ms
ReadTimeout: 5000 # 在连接创建好以后,调用接口的超时时间,单位:ms
MaxAutoRetries: 1 # 最大重试次数
MaxAutoRetriesNextServer: 2 # 切换到下个微服务实例的重试次数
# 当请求到某个微服务5s,超时后会进行重试,先重试连接自己当前的这个实例
# 如果当前重试失败1次,则会切换到访问集群中的下一个微服务实例,切换最大为2次
logging:
level:
com.imooc.api.controller.user.UserControllerApi: debug
# root: debug 日志打印级别
# 配置feign
feign:
client:
config:
# 配置服务提供方的名称
service-user:
logger-level: full
重启所有服务 调用慕课新闻·自媒体接口8001api门户端→文章详情 articleId:2006117B57WRZGHH
09:19.009 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] <— HTTP/1.1 200 (590ms)
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] connection: keep-alive
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] content-type: application/json
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] date: Mon, 05 Aug 2024 09:09:19 GMT
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] keep-alive: timeout=60
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] transfer-encoding: chunked
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] vary: Access-Control-Request-Headers
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] vary: Access-Control-Request-Method
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] vary: Origin
09:19.011 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds]
09:19.013 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] {“status”:200,”msg”:”操作成功!”,”success”:true,”data”:[{“id”:”200628AFYM7AGWPH”,”nickname”:”我是慕课网”,”face”:”https://imooc-news-dev.oss-cn-shanghai.aliyuncs.com/images/abc/200628AFYM7AGWPH/2007088XH2WT7GXP.png","activeStatus":1,"myFollowCounts":null,"myFansCounts":null}]}
09:19.013 [http-nio-8001-exec-7] DEBUG c.i.a.c.user.UserControllerApi - [UserControllerApi#queryByIds] <— END HTTP (286-byte body)
09:19.518 [PollingServerListUpdater-0] INFO c.n.config.ChainedDynamicProperty - Flipping property: service-user.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
09:21.013 [scheduling-1] INFO c.imooc.api.aspect.ServiceLogAspect - 开始执行 class com.imooc.article.service.impl.ArticleServiceImpl.updateAppointToPublish
阐述断路器及概念【hystrix】
断路器
- Hystrix
- 提供容错机制,避免微服务系统雪崩
服务熔断与降级
模拟服务故障【hystrix】
慕课新闻·自媒体接口8001api 同上
会报Timeout超时的异常
service-user com/imooc/user/controller/UserController.java
@Value("${server.port}")
private String myPort;
@Override
public GraceJSONResult queryByIds(String userIds) {
// 1.手动触发异常
int a = 1/0;
// 2.模拟超时异常
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("myPort=" + myPort);
if (StringUtils.isBlank(userIds)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
for (String userId : userIdList){
//获得用户基本信息
AppUserVO userVO = getBasicUserInfo(userId);
// 3.添加到publisherList
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
service-api pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
服务提供者熔断【hystrix】
service-user com/imooc/user/controller/UserController.java
@Value("${server.port}")
private String myPort;
// 添加熔断机制 一旦熔断会有替补方法[降级的方法]
@HystrixCommand(fallbackMethod = "queryByIdsFallback")
@Override
public GraceJSONResult queryByIds(String userIds) {
// 1.手动触发异常
int a = 1/0;
// 2.模拟超时异常
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("myPort=" + myPort);
if (StringUtils.isBlank(userIds)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
for (String userId : userIdList){
//获得用户基本信息
AppUserVO userVO = getBasicUserInfo(userId);
// 3.添加到publisherList
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
public GraceJSONResult queryByIdsFallback(String userIds) {
System.out.println("进入降级方法:queryByIdsFallback");
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
for (String userId : userIdList){
// 手动构建空对象,详情页所展示的用户信息可有可无 返回空对象
AppUserVO userVO = new AppUserVO();
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
service-user com/imooc/user/Application.java
package com.imooc.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@MapperScan(basePackages = "com.imooc.user.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient // 开启eureka client 注册到server中
@EnableCircuitBreaker // 开启hystrix的熔断机制
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
===============================================================
进入降级方法
service-user application.yml
# 配置hystrix
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 设置hystrix超时时间,超过2秒触发降级
全局降级【hystrix】
只需要return一个错误就行了没必要100个方法写100个降级
{ status: 555,
msg: “”全局降级:系统繁忙,请稍后再试!””
success: false,
data: null}
service-user com/imooc/user/controller/UserController.java
@RestController
@DefaultProperties(defaultFallback = "defaultFallback")
public class UserController extends BaseController implements UserControllerApi {
final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
// 其他方法一旦发现异常就会进入这个方法里面 全局唯一 其他的降级方法要注释
public GraceJSONResult defaultFallback(){
return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_GLOBAL);
}
......
}
// 改动是为了不报错空指针异常 因为已经变成了全局降级 降级的错误信息要调整
@Autowired
private UserControllerApi userControllerApi;
//面向接口 UserControllerApi
// 发起远程调用,获得用户的基本信息
/* private List<AppUserVO> getPublisherList(Set idSet) {
GraceJSONResult bodyResult = userControllerApi.queryByIds(JsonUtils.objectToJson(idSet));
List<AppUserVO> publisherList = null;
if (bodyResult.getStatus() == 200) {
String userJson = JsonUtils.objectToJson(bodyResult.getData());
publisherList = JsonUtils.jsonToList(userJson, AppUserVO.class);
}*/ else {
publisherList = new ArrayList<>();
}
return publisherList;
}
dev-common com/imooc/grace/result/ResponseStatusEnum.java
// 系统错误,未预期的错误 555
SYSTEM_ERROR(555, false, "系统繁忙,请稍后再试!"),
SYSTEM_OPERATION_ERROR(556, false, "操作失败,请重试或联系管理员"),
SYSTEM_RESPONSE_NO_INFO(557, false, ""),
SYSTEM_ERROR_GLOBAL(558, false, "全局降级:系统繁忙,请稍后再试!"),
SYSTEM_ERROR_FEIGN(559, false, "客户端Feign降级:系统繁忙,请稍后再试!"),
SYSTEM_ERROR_ZUUL(560, false, "请求系统过于繁忙,请稍后再试!");
服务调用者降级【hystrix】
service-article application.yml
# 配置feign
feign:
client:
config:
# 配置服务提供方的名称
service-user:
logger-level: full
hystrix: #打开feign客户端的内置hystrix
enabled: true
service-article com/imooc/article/Application.java //【增加一个@EnableHystrix】
package com.imooc.article;
import com.rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import tk.mybatis.spring.annotation.MapperScan;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.article.mapper")
@ComponentScan(basePackages = {"com.imooc","org.n3r.idworker"})
@EnableEurekaClient
//@RibbonClient(name = "service-user", configuration = MyRule.class) //微服务名称
@EnableFeignClients({"com.imooc"})
@EnableHystrix
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
====================================================================================
service-article:8001 进入客户端(服务调用者)的降级方法
service-api com/imooc/api/controller/user/UserControllerApi.java//【@FeignClient增加fallbackFactory】
package com.imooc.api.controller.user;
import com.imooc.api.config.MyServiceList;
import com.imooc.api.controller.user.fallbacks.UserControllerFactoryFallback;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.pojo.bo.RegistLoginBO;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Api(value = "用户信息相关Controller",tags = {"用户信息相关Controller"})
@RequestMapping("user") //fallbackFactory所有方法的降级
@FeignClient(value = MyServiceList.SERVICE_USER, fallbackFactory = UserControllerFactoryFallback.class ) //作为客户端直接调用
public interface UserControllerApi {
@ApiOperation(value = "获得用户基本信息",notes = "获得用户基本信息",httpMethod = "POST")
@PostMapping("/getUserInfo")
public GraceJSONResult getUserInfo(@RequestParam String userId);
@ApiOperation(value = "获得用户账户信息",notes = "获得用户账户信息",httpMethod = "POST")
@PostMapping("/getAccountInfo")
public GraceJSONResult getAccountInfo(@RequestParam String userId);
// @ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
// @PostMapping("/updateUserInfo")
// public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO,
// @RequestParam BindingResult result);
@ApiOperation(value = "修改/完善用户信息",notes = "修改/完善用户信息",httpMethod = "POST")
@PostMapping("/updateUserInfo")
public GraceJSONResult updateUserInfo(@RequestBody @Valid UpdateUserInfoBO updateUserInfoBO);
@ApiOperation(value = "根据用户的ids查询用户列表",notes = "根据用户的ids查询用户列表",httpMethod = "GET")
@GetMapping("/queryByIds")
public GraceJSONResult queryByIds(@RequestParam String userIds);
}
service-api com/imooc/api/controller/user/fallbacks/UserControllerFactoryFallback.java
package com.imooc.api.controller.user.fallbacks;
import com.imooc.api.controller.user.UserControllerApi;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.pojo.bo.UpdateUserInfoBO;
import com.imooc.pojo.vo.AppUserVO;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
@Component //这个类让容器加载
public class UserControllerFactoryFallback implements FallbackFactory<UserControllerApi> {
@Override
public UserControllerApi create(Throwable throwable) {
// 重写的过程就是降级的过程
return new UserControllerApi() {
//SYSTEM_ERROR_FEIGN(559, false, "客户端Feign降级:系统繁忙,请稍后再试!")
@Override
public GraceJSONResult getUserInfo(String userId) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
}
@Override
public GraceJSONResult getAccountInfo(String userId) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
}
@Override
public GraceJSONResult updateUserInfo(@Valid UpdateUserInfoBO updateUserInfoBO) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_ERROR_FEIGN);
}
@Override
public GraceJSONResult queryByIds(String userIds) {
System.out.println("进入客户端(服务调用者)的降级方法");
List<AppUserVO> publisherList = new ArrayList<>();
return GraceJSONResult.ok(publisherList);
}
};
}
}
自动触发熔断隔离与恢复【hystrix】
service-user application.yml #【配置熔断器】
# 配置hystrix
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 设置hystrix超时时间,超过2秒触发降级
circuitBreaker: # 配置断路器
enabled: true
requestVolumeThreshold: 10 # 触发熔断最小请求次数,默认:20
sleepWindowInMilliseconds: 15000 # 熔断后过几秒后尝试半开状态(请求重试),默认:5s
errorThresholdPercentage: 50 # 触发熔断的失败率(异常率/阈值),默认:50
service-user com/imooc/user/controller/UserController.java //[FIXME:]
@Value("${server.port}")
private String myPort;
// 添加熔断机制 一旦熔断会有替补方法[降级的方法]
@HystrixCommand//(fallbackMethod = "queryByIdsFallback")
@Override
public GraceJSONResult queryByIds(String userIds) {
// 1.手动触发异常
int a = 1/0;
// 2.模拟超时异常
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("myPort=" + myPort);
if (StringUtils.isBlank(userIds)){
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_NOT_EXIST_ERROR);
}
List<AppUserVO> publisherList = new ArrayList<>();
List<String> userIdList = JsonUtils.jsonToList(userIds, String.class);//传过来两个用户的id
// FIXME: 仅用于dev测试,硬编码动态判断来抛出异常
if (userIdList.size() > 1){
System.out.println("出现异常~~");
throw new RuntimeException("出现异常~~");
}
for (String userId : userIdList){
//获得用户基本信息
AppUserVO userVO = getBasicUserInfo(userId);
// 3.添加到publisherList
publisherList.add(userVO);
}
return GraceJSONResult.ok(publisherList);
}
微服务网关【zuul】维护微服务的ip地址
服务网关
- Zuul (祖尔)
- 微服务的网关,可以实现动态路由、过滤器等功能
构建网关微服务【zuul】
springcloud-zuul-server com/imooc/zuul/Application.java
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
//@EnableZuulServer
@EnableZuulProxy // @EnableZuulProxy是@EnableZuulServer的一个增强升级版,当zuul和eureka、ribbon等组件共同使用,则使用增强版即可
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
springcloud-zuul-server logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/springcloud-zuul"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/zuul.%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>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
service-api pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
springcloud-zuul-server pom.xml 【exclusions是重点】
<?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>imooc-news-dev</artifactId>
<groupId>com.imooc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-zuul-server</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version> <!--排除包-->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-bus-amqp</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
</project>
springcloud-zuul-server application.yml
############################################################
#
# 网关zuul
# web访问端口号 约定:7070
#
############################################################
server:
port: 7070
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-zuul-server
redis:
database: 0
host: 192.168.1.201
port: 6379
password: 123456
zipkin:
# 配置zipkin采集的服务地址,数据会发送到这里
base-url: http://192.168.1.2:9411/
sender:
# 数据采集的传输通信方式,web http的形式
type: web
sleuth:
sampler:
# 数据采样比例(百分数),0~1
probability: 1
zuul-server com/imooc/zuul/controller/HelloController.java
package com.imooc.zuul.controller;
import com.imooc.grace.result.GraceJSONResult;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/hello")
public Object hello() {
return GraceJSONResult.ok();
}
}
配置路由【zuul】
zuul-server application.yml
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
path: /service-article/** # 请求路径(前缀)
url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
localhost:8001/portal/article/detail?articleId=2006117B57WRZGHH
直接可以访问到详情数据
微服务网关→ 7070
// service-article: /service-article/** # 请求路径(前缀**)
# localhost:7070/service-article/portal/detail?articleId=2006117B57WRZGHH
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
path: /service-article/** # 请求路径(前缀)
url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
# localhost:7070/api/service-article/portal/detail?articleId=2006117B57WRZGHH
配置微服务实例的路由【zuul】
在路由规则里面的 url: http://192.168.1.2:8001 很容易发生变化
所以直接去请求eureka的SERVICE-USER/ARTICLE
springcloud-zuul-server pom.xml 【取消exclusions注释 包含eureka client】
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version> <!--排除包-->
</dependency>
zuul-server application.yml
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
springcloud-zuul-server com/imooc/zuul/Application.java 【打开@EnableEurekaClient】
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
@EnableEurekaClient
//@EnableZuulServer
@EnableZuulProxy // @EnableZuulProxy是@EnableZuulServer的一个增强升级版,当zuul和eureka、ribbon等组件共同使用,则使用增强版即可
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
zuul-server application.yml 【实现service-id进行请求转发 ip发生变化没有关系】
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: # 配置微服务的路由id,微服务的实例id
path: /service-article/** # 请求路径(前缀)
service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 简化版本 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
# service-article: # 配置微服务的路由id,微服务的实例id
# path: /service-article/** # 请求路径(前缀)
# service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
过滤器【zuul】网端ip黑名单拦截
zuul-server com/imooc/zuul/filters/MyFilter.java
package com.imooc.zuul.filters;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
/**
* 构建zuul的自定义过滤器
*/
@Component
public class MyFilter extends ZuulFilter {
/**
* 定义过滤器的类型
* pre: 在请求被路由之前执行
* route: 在路由请求的时候执行
* post: 请求路由以后执行
* error: 处理请求时发生错误的时候执行
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器执行的顺序,配置多个有顺序的过滤
* 执行顺序从小到大
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 是否开启过滤器
* true:开启
* false:禁用
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的业务实现
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
System.out.println("display pre zuul filter...");
return null; // 没有意义可以不用管。
}
}
localhost:7070/api/service-article/portal/detail?articleId=2006117B57WRZGHH
刷新成功后 在zuul-7070服务console会有一行
display pre zuul filter… [启动成功]
限制ip黑名单的频繁请求【zuul】
zuul-server application.yml 【增加ip请求限制的参数配置】
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: # 配置微服务的路由id,微服务的实例id
path: /service-article/** # 请求路径(前缀)
service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 简化版本 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
# service-article: # 配置微服务的路由id,微服务的实例id
# path: /service-article/** # 请求路径(前缀)
# service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
# ip请求限制的参数配置
blackIp:
continueCounts: ${counts:10} # ip连续请求的次数
timeInterval: ${interval:10} # ip判断的事件间隔,单位:秒
limitTimes: ${times:15} # 限制的事件,单位:秒
zuul-server com/imooc/zuul/filters/BlackIPFilter.java
package com.imooc.zuul.filters;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/* application.yml
* blackIp:
continueCounts: ${counts:10} # ip连续请求的次数
timeInterval: ${interval:10} # ip判断的事件间隔,单位:秒
limitTimes: ${times:15} # 限制的事件,单位:秒
* */
@Component
@RefreshScope
public class BlackIPFilter extends ZuulFilter {
@Value("${blackIp.continueCounts}")
public Integer continueCounts;
@Value("${blackIp.timeInterval}")
public Integer timeInterval;
@Value("${blackIp.limitTimes}")
public Integer limitTimes;
@Autowired
private RedisOperator redis;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("执行【ip黑名单】过滤器...");
System.out.println("continueCounts: " + continueCounts);
System.out.println("timeInterval: " + timeInterval);
System.out.println("limitTimes: " + limitTimes);
// 获得上下文对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// 获得ip
String ip = IPUtil.getRequestIp(request);
/**
* 需求:
* 判断ip在10秒内的请求次数是否超过10次
* 如果超过,则限制这个ip访问15秒,15秒以后再放行
*/
final String ipRedisKey = "zuul-ip:" + ip;
final String ipRedisLimitKey = "zuul-ip-limit:" + ip;
// 获得当前ip这个key的剩余时间
long limitLeftTime = redis.ttl(ipRedisLimitKey);
// 如果当前限制ip的key还存在剩余时间,说明这个ip不能访问,继续等待
if (limitLeftTime > 0) {
stopRequest(context);
return null;
}
// 在redis中累加ip的请求访问次数
long requestCounts = redis.increment(ipRedisKey, 1);
// 从0开始计算请求次数,初期访问为1,则设置过期时间,也就是连续请求的间隔时间
if (requestCounts == 1) {
redis.expire(ipRedisKey, timeInterval);
}
// 如果还能取得请求次数,说明用户连续请求的次数落在10秒内
// 一旦请求次数超过了连续访问的次数,则需要限制这个ip的访问
if (requestCounts > continueCounts) {
// 限制ip的访问时间
redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);
stopRequest(context);
}
return null;
}
private void stopRequest(RequestContext context) {
// 停止zuul继续向下路由,禁止请求通信
context.setSendZuulResponse(false);
context.setResponseStatusCode(200);
String result = JsonUtils.objectToJson(
GraceJSONResult.errorCustom(
ResponseStatusEnum.SYSTEM_ERROR_ZUUL));
context.setResponseBody(result);
context.getResponse().setCharacterEncoding("utf-8");
context.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
}
}
=============================================================================
dev-common com/imooc/grace/result/ResponseStatusEnum.java
SYSTEM_ERROR_ZUUL(560, false, "请求系统过于繁忙,请稍后再试!");
zuul-server application.yml 【把redis增加进来】
############################################################
#
# 网关zuul
# web访问端口号 约定:7070
#
############################################################
server:
port: 7070
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-zuul-server
redis:
database: 0
host: 192.168.1.201
port: 6379
password: 123456
zipkin:
# 配置zipkin采集的服务地址,数据会发送到这里
base-url: http://192.168.1.2:9411/
sender:
# 数据采集的传输通信方式,web http的形式
type: web
sleuth:
sampler:
# 数据采样比例(百分数),0~1
probability: 1
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
# service-article: # 配置微服务的路由id,微服务的实例id
# path: /service-article/** # 请求路径(前缀)
# service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
# ip请求限制的参数配置
blackIp:
continueCounts: ${counts:10} # ip连续请求的次数
timeInterval: ${interval:10} # ip判断的事件间隔,单位:秒
limitTimes: ${times:15} # 限制的事件,单位:秒
此时redis中会出现
zuul-ip(1) → zuul-ip → Value:1
zuul-ip-limit(1) → zuul-ip-limit: 本机地址
在zuul-7070服务里的Console 显示:
display pre zuul filter …
执行【ip黑名单】过滤器…
分布式配置中心【config】
- SpringCloud Config
- 为所有服务提供统一的配置管理服务
微服务配置一下子全部生效 - 包含配置服务端与配置客户端
配置中心的功能
- 统一管理配置
- 管理不同环境下的配置
- 动态调整配置
搭配配置中心【config】
leechenxiang/imooc-news-config (github.com)
zuul-dev.yml
blackIp:
continueCounts: 10
timeInterval: 10
limitTimes: 15
zuul-prod.yml
blackIp:
continueCounts: 40
timeInterval: 70
limitTimes: 315
springcloud-config 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">
<parent>
<artifactId>imooc-news-dev</artifactId>
<groupId>com.imooc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-config</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
</project>
springcloud-config com/imooc/config/Application.java
package com.imooc.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
==========================================================================
config-7080
springcloud-config application.yml
############################################################
#
# 配置服务Config
# web访问端口号 约定:7080
#
############################################################
server:
port: 7080
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-config
rabbitmq:
host: 192.168.1.204
port: 5672
username: admin
password: admin
virtual-host: imooc-news-dev
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
# 配置动态刷新git配置的路径终端请求地址
management:
endpoints:
web:
exposure:
include: "*"
springcloud-config logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 指定日志文件的存储地址,使用绝对路径 -->
<property name="LOG_HOME" value="/workspaces/logs/imooc-news-dev/springcloud-config"/>
<!-- Console 输出设置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%white(%d{mm:ss.SSS}) %green([%thread]) %cyan(%-5level) %yellow(%logger{36}) %magenta(-) %black(%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}/config.%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>
<!--<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">-->
<!--<appender-ref ref="CONSOLE"/>-->
<!--</logger>-->
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
springcloud-config com/imooc/config/controller/HelloController.java
package com.imooc.config.controller;
import com.imooc.grace.result.GraceJSONResult;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public Object hello() {
return GraceJSONResult.ok();
}
}
配置中心实现git配置读取【config】
springcloud-config 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">
<parent>
<artifactId>imooc-news-dev</artifactId>
<groupId>com.imooc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-config</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>imooc-news-dev-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
springcloud-config application.yml
#增加 cloud:config:server:git: uri
############################################################
#
# 配置服务Config
# web访问端口号 约定:7080
#
############################################################
server:
port: 7080
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-config
cloud:
config:
server:
git:
uri: https://github.com/leechenxiang/imooc-news-config.git
rabbitmq:
host: 192.168.1.204
port: 5672
username: admin
password: admin
virtual-host: imooc-news-dev
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
springcloud-config com/imooc/config/Application.java
package com.imooc.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
MongoAutoConfiguration.class})
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
@EnableEurekaClient
@EnableConfigServer //开启这个配置中心
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
leechenxiang/imooc-news-config (github.com)
localhost:7080/zuul-prod.yml 这里直接引用了github上面的yml
localhost:7080/master/zuul-prod.yml
zuul-dev.yml
blackIp:
continueCounts: 10
timeInterval: 10
limitTimes: 15
zuul-prod.yml
blackIp:
continueCounts: 40
timeInterval: 70
limitTimes: 315
配置客户端拉取配置
zuul-server pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
zuul-server resources/bootstrap.yml
############################################################
# 系统全局加载文件 先加载这个文件
# 网关zuul
# web访问端口号 约定:7070
#
############################################################
server:
port: 7070
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-zuul-server
redis:
database: 0
host: 192.168.1.201
port: 6379
password: 123456
cloud:
config:
label: master # github上的分支
name: zuul # 定义的服务
profile: prod # 所加载的环境变量
# uri: http://192.168.1.2:7080
discovery:
enabled: true
service-id: springcloud-config
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/**
# service-article: # 配置微服务的路由id,微服务的实例id
# path: /service-article/** # 请求路径(前缀)
# service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址
prefix: /api # 请求前缀
启动zuul-7070 //客户端连接config(server)并且动态获得了github的数据
打印控制台Console
display pre zuul filter...
执行【ip黑名单】过滤器...
// zuul-prod.yml
continueCounts: 40
timeInterval: 70
limitTimes: 315
动态刷新git配置
// zuul-prod.yml 动态修改数值
continueCounts: 35
timeInterval: 305
limitTimes: 65
在浏览器会显示更新后的数值 但是在console打印台不会显示改后的 只会显示之前的
需要重启服务才可以达到修改后的效果 显示修改的数值
zuul-server pom.xml
添加健康检测的配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
zuul-server com/imooc/zuul/filters/BlackIPFilter.java
//【开启刷新 @RefreshScope 不能全自动 需要触碰某些请求 在yaml配置动态刷新的地址】
package com.imooc.zuul.filters;
import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.RedisOperator;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/* application.yml
* blackIp:
continueCounts: ${counts:10} # ip连续请求的次数
timeInterval: ${interval:10} # ip判断的事件间隔,单位:秒
limitTimes: ${times:15} # 限制的事件,单位:秒
* */
@Component
@RefreshScope
public class BlackIPFilter extends ZuulFilter {
@Value("${blackIp.continueCounts}")
public Integer continueCounts;
@Value("${blackIp.timeInterval}")
public Integer timeInterval;
@Value("${blackIp.limitTimes}")
public Integer limitTimes;
@Autowired
private RedisOperator redis;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 2;
}
@Override
public boolean shouldFilter() {
return true;
}
}
zuul-server bootstrap.yml + springcloud-config application.yaml
# 配置动态刷新git配置的路径终端请求地址 只需要通过URL请求不用重启就可以自动刷新 后期要通过脚本运行才可【通过请求处理 postman → POST → https://localhost:7070/actuator/refresh】
management:
endpoints:
web:
exposure:
include: refresh
# 此时回到console就可以有更新后的数据显示了
消息总线概述【bus】[RabbitMQ]
Config遗留问题
- 手动刷新与业务耦合 [也可能在文章/user模块发生]
- Config配置中心的刷新去解决动态刷新
- N个微服务端需要N次手动书信
消息总线(要和config与微服务端进行配置)
- SpringCloud Bus
- 为SpringCloud Config提供增益buff
- 可以实现配置自动刷新[1k个 1w个]
配置实现消息统一发送【bus】
config把消息推給zull-server
springcloud-config pom.xml + zuul-server pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
zuul-server resources/bootstrap.yaml + springcloud-config application.yaml
rabbitmq:
host: 192.168.1.204
port: 5672
username: admin
password: admin
virtual-host: imooc-news-dev
实现刷新server端达到所有的刷新配置
【通过请求处理 postman → POST → https://localhost:7080/actuator/bus-refresh】
若想精确刷新某个服务 需要拼接
postman → POST → https://localhost:7080/actuator/bus-refresh/{微服务的实例id}:{port}
微服务实例id是yaml配置项目信息 → spring.application.name:springcloud-zuul-server
https://localhost:7080/actuator/bus-refresh/springcloud-zuul-server:7070 【目标微服务地址实现精确打击】
消息驱动概述【stream】
消息驱动
- SpringCloud Stream
- 统一封装消息的服务框架
- RabbitMQ,RocketMQ,Kafka,ActiveMQ,ZeroMQ,…
Stream消息模型

实现消费者与生产者【stream】
将zuul-server的resources/application.yml 恢复到原来配置
############################################################
#
# 网关zuul
# web访问端口号 约定:7070
#
############################################################
server:
port: 7070
tomcat:
uri-encoding: UTF-8
############################################################
#
# 配置项目信息
#
############################################################
spring:
application:
name: springcloud-zuul-server
redis:
database: 0
host: 192.168.1.201
port: 6379
password: 123456
zipkin:
# 配置zipkin采集的服务地址,数据会发送到这里
base-url: http://192.168.1.2:9411/
sender:
# 数据采集的传输通信方式,web http的形式
type: web
sleuth:
sampler:
# 数据采样比例(百分数),0~1
probability: 1
############################################################
#
# eureka client 配置信息
#
############################################################
eureka:
server:
hostname: eureka
port: 7000
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://${eureka.server.hostname}:${eureka.server.port}/eureka/
defaultZone: http://eureka-cluster-7001:7001/eureka/,http://eureka-cluster-7002:7002/eureka/,http://eureka-cluster-7003:7003/eureka/
# 路由规则: http://[网关地址]:[端口号]/[prefix]/[微服务实例id]/[请求地址路径]
zuul:
routes:
# 由于路由id和微服务实例id相同,我们可以简化转发的配置
service-article: /service-article/** # 请求路径(前缀)
# service-article: # 配置微服务的路由id,微服务的实例id
# path: /service-article/** # 请求路径(前缀)
# service-id: service-article # 请求转发的微服务实例id
# url: http://192.168.1.2:8001 # 请求转发到指定的微服务所在的ip地址(8001文章服务)
prefix: /api # 请求前缀
# ip请求限制的参数配置
blackIp:
continueCounts: ${counts:10} # ip连续请求的次数
timeInterval: ${interval:10} # ip判断的事件间隔,单位:秒
limitTimes: ${times:15} # 限制的事件,单位:秒
service-article pom.xml + service-user pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>4.1.1</version>
</dependency>
service-article application.yaml + service-user application.yaml
cloud:
stream:
bindings: # 绑定通道和交换机
myOutput: # 定义生产者的通道
# 自定义交换机的名字,也就是代码里构建的信息,交给底层mq的交换机
destination:
myInput: # 定义消费者通道
# 自定义交换机的名字,也就是消息从底层mq输入到消费端进行消费
destination:
service-article com/imooc/article/stream/MyStreamChannel.java
package com.imooc.article.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;
/**
* 声明构建通道channel
*/
@Component
public interface MyStreamChannel {
String OUTPUT = "myOutput";
String INPUT = "myInput";
@Output(MyStreamChannel.OUTPUT)
MessageChannel output();
@Input(MyStreamChannel.INPUT)
SubscribableChannel input(); //订阅能力的通道
}
service-article com/imooc/article/stream/MyStreamChannel.java
package com.imooc.article.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.stereotype.Component;
/**
* 声明构建通道channel
*/
@Component
public interface MyStreamChannel {
String OUTPUT = "myOutput";
String INPUT = "myInput";
@Output(MyStreamChannel.OUTPUT)
MessageChannel output();
@Input(MyStreamChannel.INPUT)
SubscribableChannel input();
}
service-article com/imooc/article/stream/StreamService.java
package com.imooc.article.stream;
public interface StreamService {
public void sendMsg();
public void eat(String dumpling);
}
service-article com/imooc/article/stream/StreamServiceImpl.java
package com.imooc.article.stream;
import com.imooc.pojo.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* 开启绑定器
* 绑定通道channel
*/
@Component
@EnableBinding(MyStreamChannel.class)
public class StreamServiceImpl implements StreamService {
@Autowired
private MyStreamChannel myStreamChannel;
@Override
public void sendMsg() {
AppUser user = new AppUser();
user.setId("10101");
user.setNickname("imooc");
// 消息通过绑定器发送给mq
myStreamChannel.output()
.send(MessageBuilder.withPayload(user).build());
}
@Override
public void eat(String dumpling) {
myStreamChannel.output()
.send(MessageBuilder.withPayload(dumpling).build());
}
}
service-article com/imooc/article/stream/MyStreamConsumer.java
package com.imooc.article.stream;
import com.imooc.pojo.AppUser;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component;
/**
* 构建消费端
*/
@Component
@EnableBinding(MyStreamChannel.class) //开启通道绑定
public class MyStreamConsumer {
/**
* 监听并且实现消息的消费和相关业务处理
*/
@StreamListener(MyStreamChannel.INPUT)
public void receiveMsg(AppUser user) {
System.out.println(user.toString());
}
@StreamListener(MyStreamChannel.INPUT)
public void receiveMsg(String dumpling) {
System.out.println(dumpling);
}
}
service-article com/imooc/article/controller/HelloController.java
@RestController
@RequestMapping("producer")
public class HelloController {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private StreamService streamService;
@GetMapping("/stream")
public Object stream() {
streamService.sendMsg();
for (int i = 0 ; i < 10 ; i ++ ) {
streamService.eat("我吃了第" + (i+1) + "只饺子~");
}
return "ok~~!!!";
}
消息分组与持久化【stream】
避免重复消费 分组group 组内消费者不会重复消费
Stream消息分组
service-article application.yml
cloud:
stream:
bindings: # 绑定通道和交换机
myOutput: # 定义生产者的通道
# 自定义交换机的名字,也就是代码里构建的信息,交给底层mq的交换机
destination:
myInput: # 定义消费者通道
# 自定义交换机的名字,也就是消息从底层mq输入到消费端进行消费
destination:
group: boys
------------------------------------------------------------------------------
service-user application.yml
cloud:
stream:
bindings: # 绑定通道和交换机
myOutput: # 定义生产者的通道
# 自定义交换机的名字,也就是代码里构建的信息,交给底层mq的交换机
destination:
myInput: # 定义消费者通道
# 自定义交换机的名字,也就是消息从底层mq输入到消费端进行消费
destination:
group: girls
service-article com/imooc/article/stream/StreamService.java
package com.imooc.article.stream;
public interface StreamService {
//public void sendMsg();
public void eat(String dumpling);
}
service-article com/imooc/article/stream/StreamServiceImpl.java
package com.imooc.article.stream;
import com.imooc.pojo.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
/**
* 开启绑定器
* 绑定通道channel
*/
@Component
@EnableBinding(MyStreamChannel.class)
public class StreamServiceImpl implements StreamService {
@Autowired
private MyStreamChannel myStreamChannel;
@Override
/* public void sendMsg() {
AppUser user = new AppUser();
user.setId("10101");
user.setNickname("imooc");
// 消息通过绑定器发送给mq
myStreamChannel.output()
.send(MessageBuilder.withPayload(user).build());
} */
@Override
public void eat(String dumpling) {
myStreamChannel.output()
.send(MessageBuilder.withPayload(dumpling).build());
}
}
service-article com/imooc/article/stream/MyStreamConsumer.java
package com.imooc.article.stream;
import com.imooc.pojo.AppUser;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component;
/**
* 构建消费端
*/
@Component
@EnableBinding(MyStreamChannel.class) //开启通道绑定
public class MyStreamConsumer {
/**
* 监听并且实现消息的消费和相关业务处理
*/
/* @StreamListener(MyStreamChannel.INPUT)
public void receiveMsg(AppUser user) {
System.out.println(user.toString());
} */
@StreamListener(MyStreamChannel.INPUT)
public void receiveMsg(String dumpling) {
System.out.println(dumpling);
}
}
service-article com/imooc/article/controller/HelloController.java
@RestController
@RequestMapping("producer")
public class HelloController {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private StreamService streamService;
@GetMapping("/stream")
public Object stream() {
// streamService.sendMsg();
for (int i = 0 ; i < 10 ; i ++ ) {
streamService.eat("我吃了第" + (i+1) + "只饺子~");
}
return "ok~~!!!";
}
消息不会被重复消费
service-article:8001 我吃了1-10只饺子~ 【消费的饺子总数一共10次】
service-user:8003(女生) 我吃了1 3 5 7 9只饺子~ 【饺子随机】
service-user:8013(男生) 我吃了2 4 6 8 10只饺子~ 【饺子随机】如果把service-user:8003(女生) service-user:8013(男生) 服务stop 用户微服务无法接收任何消息
但是我们定义了group → 消息是可以持久化的 当重启用户微服务之后 就会打印出刚刚已经吃的饺子了
服务器宕机 = 吃饺子中途去上厕所 回来后仍然还能吃到饺子
链路追踪概述与zipkin【sleuth】组件
链路追踪
- Sleuth
- 贯穿整个微服务系统中,追踪一个请求的过程
- zipkin 可视化控制面板
下载zipkin-server-2.12.6-exec.jar
CMD → C:\Users\Pluminary>java -jar /Users/Pluminary/Desktop/zipkin-server-2.12.6-exec.jar
https://localhost:9411/zipkin/
整合zipkin【sleuth】项目入口是网关
zuul-server pom.xml + service-article pom.xml + service-user pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
zuul-server与service-article与service-user application.yml
zipkin:
# 配置zipkin采集的服务地址,数据会发送到这里
base-url: http://192.168.1.2:9411/
sender:
# 数据采集的传输通信方式,web http的形式
type: web
sleuth:
sampler:
# 数据采样比例(百分数),0~1
probability: 1
SpringCloud章节总结
- eureka 注册中心
- ribbon 负载均衡
- feign 声明式客户端
- hystrix 熔断降级组件
- zuul 网关
- config 配置中心
- bus 消息总线
- stream 消息驱动
- zipkin + sleuth 链路追踪