项目部署
学习链接
linux下安装ffmpeg的详细教程 - 完全参照这个安装的
FFmpeg视频处理入门教程----从安装到使用(Linux版)
linux下ffmpeg安装教程(小学生都能看懂)
文件上传java报Processing of multipart/form-data request failed. java.io.EOFException: Unexpected EOF read
1.安装ffmpeg
linux centos下安装ffmpeg的详细教程
1、下载解压
wget http://www.ffmpeg.org/releases/ffmpeg-3.1.tar.gz
tar -zxvf ffmpeg-3.1.tar.gz
2、 进入解压后目录,输入如下命令/usr/local/ffmpeg为自己指定的安装目录
cd ffmpeg-3.1
./configure --prefix=/usr/local/ffmpeg
make && make install
3、配置变量
vi /etc/profile
在最后PATH添加环境变量:
export PATH=$PATH:/usr/local/ffmpeg/bin
保存退出
查看是否生效
source /etc/profile 设置生效
4、查看版本
ffmpeg -version # 查看版本
注意,若安装过程中出现以下错误
yasm/nasm not found or too old. Use –disable-yasm for a crippled build. If you think configure made a mistake, make sure you are using the latest version from Git. If the latest version fails, report the problem to the ffmpeg-user@ffmpeg.org mailing list or IRC #ffmpeg on irc.freenode.net. Include the log file “config.log” produced by configure as this will help solve the problem.
需要安装 yasm
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar -zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0
./configure
make && make install
2. springboot + maven 多环境配置文件
因为分为本地开发环境 和 正式环境,如果是每次切换环境,还要把配置文件的参数改来改去的话,就太麻烦了,因此使用springboot支持的profile指定多环境配置。
pom.xml
- 需要在maven中使用Profiles标签配置
本地开发dev环境
和线上正式prod环境
,方便使用maven切换环境,并且此时可在application.properties中使用@profileActive@来引用maven选择的环境,让指定的环境的配置文件生效。 - 在build标签下,创建resources标签,再在resource标签下创建resource,将指定环境下的配置文件纳入到打包中(这步可以不做)
<?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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.easypan</groupId> <artifactId>easypan</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>easypan</name> <description>easypan</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <skipTests>true</skipTests> <springboot.version>2.6.1</springboot.version> <mybatis.version>1.3.2</mybatis.version> <logback.version>1.2.10</logback.version> <mysql.version>8.0.23</mysql.version> <aspectjweaver.version>1.9.4</aspectjweaver.version> <okhttp3.version>3.2.0</okhttp3.version> <fastjson.version>1.2.66</fastjson.version> <commons.lang3.version>3.4</commons.lang3.version> <commons.codec.version>1.9</commons.codec.version> <commons.io.version>2.5</commons.io.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </exclusion> </exclusions> </dependency> <!--邮件发送--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>${springboot.version}</version> </dependency> <!--redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${springboot.version}</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.version}</version> </dependency> <!-- 数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- 日志版本 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback.version}</version> </dependency> <!--切面--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectjweaver.version}</version> </dependency> <!--okhttp--> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp3.version}</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <!--apache common--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons.lang3.version}</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>${commons.codec.version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons.io.version}</version> </dependency> </dependencies> <profiles> <profile> <!-- 开发环境 --> <id>dev</id> <properties> <profileActive>dev</profileActive> </properties> <!-- 默认激活的环境 --> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <!-- 生产环境 --> <id>prod</id> <properties> <profileActive>prod</profileActive> </properties> </profile> </profiles> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>application-${profileActive}.yml</include> <include>application.yml</include> <include>**/*.xml</include> <include>application.properties</include> <include>application-${profileActive}.properties</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.6.RELEASE</version> <executions> <execution> <goals> <goal> repackage </goal> </goals> </execution> </executions> <configuration> <mainClass>com.easypan.EasyPanApplication</mainClass> </configuration> </plugin> </plugins> </build> </project>
application.properties
使用@profileActive@可以引用使用maven选择的环境
#spring.profiles.active=prod
spring.profiles.active=@profileActive@
application-dev.properties
本地开发环境配置
# 应用服务 WEB 访问端口
server.port=7090
server.servlet.context-path=/api
#session过期时间 60M 一个小时
server.servlet.session.timeout=PT60M
#处理favicon
spring.mvc.favicon.enable=false
spring.servlet.multipart.max-file-size=15MB
spring.servlet.multipart.max-request-size=15MB
#错误页处理
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/easypan?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.pool-name=HikariCPDatasource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
#发送邮件配置相关
# 配置邮件服务器的地址 smtp.qq.com
spring.mail.host=smtp.qq.com
# 配置邮件服务器的端口(465或587)
spring.mail.port=587
# 配置用户的账号
spring.mail.username=1255112011@qq.com
# 配置用户的密码
spring.mail.password=填入自己的授权码
# 配置默认编码
spring.mail.default-encoding=UTF-8
# SSL 连接配置
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# 开启 debug,这样方便开发者查看邮件发送日志
spring.mail.properties.mail.debug=true
#邮件配置结束
#Spring redis配置
# Redis数据库索引(默认为0)
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=2000
#项目目录
project.folder=D:/document/easypan/easypan-java/
#日志级别配置
log.root.level=info
#超级管理员id
admin.emails=1255112011@qq.com
#是否是开发环境
dev=true
##qq登陆相关##
qq.app.id=123456
qq.app.key=123456
qq.url.authorization=https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s
qq.url.access.token=https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
qq.url.openid=https://graph.qq.com/oauth2.0/me?access_token=%S
qq.url.user.info=https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s
qq.url.redirect=http://easypan.wuhancoder.com/qqlogincalback
application-prod.properties
# 应用服务 WEB 访问端口
server.port=7091
server.servlet.context-path=/api
#session过期时间 60M 一个小时
server.servlet.session.timeout=PT60M
#处理favicon
spring.mvc.favicon.enable=false
spring.servlet.multipart.max-file-size=15MB
spring.servlet.multipart.max-request-size=15MB
#错误页处理
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
#数据库配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/easypan?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.pool-name=HikariCPDatasource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
#发送邮件配置相关
# 配置邮件服务器的地址 smtp.qq.com
spring.mail.host=smtp.qq.com
# 配置邮件服务器的端口(465或587)
spring.mail.port=587
# 配置用户的账号
spring.mail.username=1255112011@qq.com
# 配置用户的密码
spring.mail.password=填入自己的授权码
# 配置默认编码
spring.mail.default-encoding=UTF-8
# SSL 连接配置
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# 开启 debug,这样方便开发者查看邮件发送日志
spring.mail.properties.mail.debug=true
#邮件配置结束
#Spring redis配置
# Redis数据库索引(默认为0)
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=填写redis密码
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=2000
#项目目录
project.folder=/usr/local/blog/easypan/backend/
#日志级别配置
log.root.level=info
#超级管理员id
admin.emails=1255112011@qq.com
#是否是开发环境
dev=true
##qq登陆相关##
qq.app.id=123456
qq.app.key=123456
qq.url.authorization=https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&state=%s
qq.url.access.token=https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
qq.url.openid=https://graph.qq.com/oauth2.0/me?access_token=%S
qq.url.user.info=https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s
qq.url.redirect=http://easypan.wuhancoder.com/qqlogincalback
logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10 minutes">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<appender name="stdot" class="ch.qos.logback.core.ConsoleAppender">
<!-- <layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%-40.40logger{39}][%M][%L]-> %m%n</pattern>
</layout>-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- %d 是时间、
%p 是日志级别 %5p指定日志级别最小宽度为5且右对齐(不足时左补空)、
${PID:- } 的意思是从上下文里面去取进程id,如果能取到的就取,娶不到的话,就是后面的空字符串、
%t 是线程名称, %5.10t 的意思是最小占5位最多占10位且默认情况下会右对齐(如果需要左对齐,应该写%-5.10t),但一般写%10.10t对的比较整齐一点,如果超过了最大长度,左边会被截掉。对齐的含义只会出现在当前的内容长度不够最小位数的情况
%m是打印的日志内容
%n是换行
%logger 输出日志的logger名,%logger{36} 表示logger名字最长36个字符,否则按照句点分割,%logger{0} 设置为0表示只输入logger最右边,。
%line
%clr是 ColorConverter 中实现的(如果需要修改,那么需要继承ColorConverter修改里面里面的实现即可),可以写的颜色有faint、red、green、yellow、blue、magenta、cyan
-->
<pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:-取不到}){magenta} %clr(---){faint} %clr([%5.10t]){yellow} [%logger{0}:%line] %m%n</pattern>
</encoder>
</appender>
<springProperty scope="context" name="log.path" source="project.folder"/>
<springProperty scope="context" name="log.root.level" source="log.root.level"/>
<property name="LOG_FOLDER" value="logs"/>
<property name="LOG_FILE_NAME" value="easypan.log"/>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${LOG_FOLDER}/${LOG_FILE_NAME}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${LOG_FOLDER}/${LOG_FILE_NAME}.%d{yyyyMMdd}.%i</FileNamePattern>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>20MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<charset>utf-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%c][%M][%L]-> %m%n</pattern>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>
<logger name="com.easypan.mappers" level="debug" additivity="false">
<appender-ref ref="stdot"/>
</logger>
<root level="${log.root.level}">
<appender-ref ref="stdot"/>
<appender-ref ref="file"/>
</root>
</configuration>
3. 配置nginx
配置要点
有两个东西需要配置:
-
网盘的前端项目访问 和 后台接口请求转发 配置
-
文件上传配置参数(遇到过问题:上传到5M的时候,就停了,然后报错如下。本地是可以上传超过5M的,因此怀疑是nginx的配置相关的问题,可参考这篇解决:文件上传java报Processing of multipart/form-data request failed. java.io.EOFException: Unexpected EOF read
Caused by: java.io.IOException: org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException: Processing of multipart/form-data request failed. java.io.EOFException at org.apache.catalina.connector.Request.parseParts(Request.java:2966) at org.apache.catalina.connector.Request.getParts(Request.java:2823) at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1098) at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:95) ... 43 common frames omitted Caused by: org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException: Processing of multipart/form-data request failed. java.io.EOFException at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:292) at org.apache.catalina.connector.Request.parseParts(Request.java:2921) ... 46 common frames omitted Caused by: org.apache.catalina.connector.ClientAbortException: java.io.EOFException at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:322) at org.apache.catalina.connector.InputBuffer.checkByteBufferEof(InputBuffer.java:600) at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:340) at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.makeAvailable(MultipartStream.java:975) at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.read(MultipartStream.java:879) at java.io.InputStream.read(InputStream.java:101) at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:97) at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:288) ... 47 common frames omitted Suppressed: org.apache.catalina.connector.ClientAbortException: java.io.EOFException at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:322) at org.apache.catalina.connector.InputBuffer.checkByteBufferEof(InputBuffer.java:600) at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:340) at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.makeAvailable(MultipartStream.java:975) at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.close(MultipartStream.java:919) at org.apache.tomcat.util.http.fileupload.MultipartStream$ItemInputStream.close(MultipartStream.java:898) at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:117) ... 48 common frames omitted Caused by: java.io.EOFException: null at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1294) at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1206) at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:805) at org.apache.coyote.http11.Http11InputBuffer.access$400(Http11InputBuffer.java:42) at org.apache.coyote.http11.Http11InputBuffer$SocketInputBuffer.doRead(Http11InputBuffer.java:1172) at org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityInputFilter.java:101) at org.apache.coyote.http11.Http11InputBuffer.doRead(Http11InputBuffer.java:249) at org.apache.coyote.Request.doRead(Request.java:640) at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:317) ... 55 common frames omitted Caused by: java.io.EOFException: null at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1294) at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1206) at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:805) at org.apache.coyote.http11.Http11InputBuffer.access$400(Http11InputBuffer.java:42) at org.apache.coyote.http11.Http11InputBuffer$SocketInputBuffer.doRead(Http11InputBuffer.java:1172) at org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityInputFilter.java:101) at org.apache.coyote.http11.Http11InputBuffer.doRead(Http11InputBuffer.java:249) at org.apache.coyote.Request.doRead(Request.java:640) at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:317) ... 55 common frames omitted
nginx配置
./sbin/nginx -t 检查nginx配置文件是否有语法错误
./sbin/nginx -s reload 当修改完nginx配置文件后,重新载入配置文件
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 50m;
client_body_buffer_size 5M;
client_header_timeout 1m;
client_body_timeout 1m;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 4;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
server {
listen 80;
server_name 119.23.61.24;
location / {
root /usr/local/blog/bootblog/web/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~ /api/ {
proxy_pass http://119.23.61.24:9091/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 81;
server_name 119.23.61.24;
## 个人博客前端项目
location / {
root /usr/local/blog/bootblog/admin/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
## 个人博客后台接口
location ^~ /api/ {
proxy_pass http://119.23.61.24:9091/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /img/ {
root /usr/local/blog/bootblog/res/img/;
autoindex on;
autoindex_exact_size off;
autoindex_format html;
autoindex_localtime on;
}
}
server {
listen 82;
server_name 119.23.61.24;
location / {
proxy_pass http://119.23.61.24:8080/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 83;
server_name 119.23.61.24;
location / {
root /usr/local/blog/bootblog/res/img/;
autoindex on;
index a.html; #指明index文件,默认为index.html,如果此文件不存在,访问域名时会显示目录结构
autoindex_exact_size off;
autoindex_format json; #指明返回的为json格式,也可以是html格式
autoindex_localtime on;
}
}
server {
listen 7090;
server_name 119.23.61.24;
## 网盘前端项目
location / {
root /usr/local/blog/easypan/web/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
## 后台接口请求转发
location ^~ /api/ {
proxy_pass http://localhost:7091; ## 注意这里的7091后面没有带/,所以匹配到的请求的请求路径/api/xxx都会拼接到7091后面去,即形成了http://localhost:7091/api/xxx
## 添加如下的配置,否则会出现上传超过5M就报错了
client_max_body_size 500m;
proxy_max_temp_file_size 1024m;
#client_header_buffer_size 24k;
client_body_buffer_size 24k;
#client_max_body_size 200m;
client_body_timeout 3600s;
client_body_temp_path /temp;
resolver_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
4. 启动项目
nohup java -jar easypan.jar > /dev/null &
ps -ef|grep java # 查看是否起来了
netstat -anp |grep 7091 # 查看端口是否占用了
5.访问
访问即可:http://119.23.61.24:7090/login
后台项目
qq登录
步骤:
- 浏览器点击QQ登陆按钮,服务器返回QQ登陆网址(手机扫码界面)。
- 用户进行QQ登陆,登陆成功重定向服务器callback网址,并携带授权code与state,state就是next参数,用于登陆成功后返回的页面。
- 向服务器访问callback?code=xxx&state=xxx,凭借code向QQ服务器请求access token,然后QQ服务器返回access token给服务器,然后服务器凭借着access token向QQ服务器请求用户的openid,再返回用户的openid(用户的唯一身份表示)。
- 下面判断用户是否第一次使用QQ登陆
- 如果用户不是第一次QQ登陆,则登陆成功,返回JWT token,用户跳转到state指明的页面。
- 如果用户是第一次使用QQ登陆,则声称绑定用户身份的access token并返回浏览器:
然后用户携带手机号,密码,短信验证码,access token请求绑定QQ用户身份给服务器,在服务器中查询如果存在用户数据,直接绑定,如果不在就创建用户并绑定身份,随后从服务器返回登陆成功的JWT token,用户跳转到state指明页面。
放置qq登录图标,绑定点击事件,向后台获取qq登录授权url
如下放置qq登录的图标,并且绑定点击事件,触发登录。所谓的触发登录其实就是请求后台接口,来获取qq登录的授权的url
,然后前端再让用户重定向到这个qq登录的授权的url
,用户重定向到qq登录授权的url后的这个时候跟我们没关系了,这时qq它自己要完成验证用户身份
(它怎么完成认证的,我们并不关心),并且qq在用户确认授权后,就会重定向到我们指定的页面
。
- qq登录的授权的url:
https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id={appId}&redirect_uri={redirectUri}&state={state}
- 这个授权url的意思就代表:用户向qq说,我要以qq用户的身份登录到 指定appId的客户端去,qq你帮我处理一下,然后接着
qq肯定要对这个用户进行身份认证
(你是哪个qq用户),qq确认是自己的用户后,然后看一下这个appId的客户端有没有在自己这里登记过
(需要申请qq互联登录),再看下这个重定向地址是不是在登记的时候记录了
,发现都没问题,才会让用户重定向到redirectUri的地址,并且会携带code授权码
和我们带过去的 state
,如:http://easypan.wuhancoder.com/qqlogincalback?code=xxx&state=1234
- 这个授权url的意思就代表:用户向qq说,我要以qq用户的身份登录到 指定appId的客户端去,qq你帮我处理一下,然后接着
- 下面有一个
callbackUrl的参数
,这个是当用户未登录我们的系统时,然后用户在地址栏自己输出了想去的地址,系统发现用户未登录,就会让用户重定向到登录页面,并且把想去的地址用url编码的方式处理,拼接在地址栏后面(这个在axios的响应拦截器中做
),然后随着获取登录授权url时,把这个callbackUrl存放到session(以state为key,callback为value,其中state是随机生成的)
,也就是说此时后台已经记录到当前这个用户想要去的地址。<div class="login-btn-qq" v-if="opType == 1"> 快捷登录 <img src="@/assets/qq.png" @click="qqLogin" /> </div> <script setup> import { ref, reactive, getCurrentInstance, nextTick, onMounted } from "vue"; import { useRouter, useRoute } from "vue-router"; import md5 from "js-md5"; const { proxy } = getCurrentInstance(); const router = useRouter(); const route = useRoute(); const api = { checkCode: "/api/checkCode", sendMailCode: "/sendEmailCode", register: "/register", login: "/login", resetPwd: "/resetPwd", qqlogin: "/qqlogin", }; //QQ登录 const qqLogin = async () => { let result = await proxy.Request({ url: api.qqlogin, params: { callbackUrl: route.query.redirectUrl || "", }, }); if (!result) { return; } proxy.VueCookies.remove("userInfo"); document.location.href = result.data; }; </script>
获取qq登录授权url接口
随机生成的state,然后把用户想要去的地址,保存到session中,把qq登录授权的url返回给前端,让前端作地址跳转。
@RequestMapping("qqlogin")
@GlobalInterceptor(checkLogin = false, checkParams = true)
public ResponseVO qqlogin(HttpSession session, String callbackUrl) throws UnsupportedEncodingException {
String state = StringTools.getRandomString(Constants.LENGTH_30);
if (!StringTools.isEmpty(callbackUrl)) {
session.setAttribute(state, callbackUrl);
}
String url = String.format(appConfig.getQqUrlAuthorization(), appConfig.getQqAppId(), URLEncoder.encode(appConfig.getQqUrlRedirect(), "utf-8"), state);
return getSuccessResponseVO(url);
}
获取code授权码 和 state
上面用户通过后台返回的qq登录授权url,重定向到qq登录授权的页面,当用户点击登录授权后,qq就会让用户重定向到我们指定的重定向地址redirectUri(这个地址qq也必须知道,登记在了qq互联),并且在地址后面拼接上请求参数code和state
,
- 如:
http://easypan.wuhancoder.com/qqlogincalback?code=xxx&state=1234
,这个code就代表qq向我们的应用网站 授权了这个用户,我们后面要拿着这个code去获取AccessToken访问令牌的。这个state可以用于记录用户登陆前的一些状态信息(因为qq重定向后,会把这个参数原封不动的带过来)
重定向的地址会对应到我们的vue项目的一个页面
,在这个页面里,就可以拿到qq返回给我们的code和state,然后把code和state传给后台,让后台去获取访问令牌,进而获取用户的openid和用户相关信息。也就是,在后台没返回之前,用户将会停留在下面页面一段时间。
<template>
<div>登录中,请勿刷新页面</div>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
const api = {
logincallback: "/qqlogin/callback",
};
const login = async () => {
let result = await proxy.Request({
url: api.logincallback,
params: router.currentRoute.value.query,
errorCallback: () => {
router.push("/");
},
});
if (!result) {
return;
}
let redirectUrl = result.data.callbackUrl || "/";
if (redirectUrl == "/login") {
redirectUrl = "/";
}
proxy.VueCookies.set("userInfo", result.data.userInfo, 0);
console.log("路径",redirectUrl);
router.push(redirectUrl);
};
login();
</script>
<style lang="scss" scoped></style>
获取qq用户的信息
用户授权后,重定向到vue前端的页面,在此页面中将地址栏路径后面的code和state获取到,并传给后台,后台将code传给qq后台,来获取访问令牌。
@RequestMapping("qqlogin/callback")
@GlobalInterceptor(checkLogin = false, checkParams = true)
public ResponseVO qqLoginCallback(HttpSession session,
@VerifyParam(required = true) String code,
@VerifyParam(required = true) String state) {
// 获取用户的qq信息,记录用户
SessionWebUserDto sessionWebUserDto = userInfoService.qqLogin(code);
// 将用户身份存入会话
session.setAttribute(Constants.SESSION_KEY, sessionWebUserDto);
// 1. 用户登陆前的状态信息(使用state从会话中获取)
// 2. 当前用户信息
Map<String, Object> result = new HashMap<>();
result.put("callbackUrl", session.getAttribute(state));
result.put("userInfo", sessionWebUserDto);
return getSuccessResponseVO(result);
}
获取登录信息
@Override
public SessionWebUserDto qqLogin(String code) {
// 根据code,获取访问令牌
String accessToken = getQQAccessToken(code);
// 根据访问令牌,获取 openid 用户唯一id
String openId = getQQOpenId(accessToken);
// 根据qq唯一id 判断是否第一次登录我们的网站
UserInfo user = this.userInfoMapper.selectByQqOpenId(openId);
String avatar = null;
if (null == user) {
// 如果是第一次登录网站,则获取qq用户信息
QQInfoDto qqInfo = getQQUserInfo(accessToken, openId);
user = new UserInfo();
String nickName = qqInfo.getNickname();
nickName = nickName.length() > Constants.LENGTH_150 ? nickName.substring(0, 150) : nickName;
avatar = StringTools.isEmpty(qqInfo.getFigureurl_qq_2()) ? qqInfo.getFigureurl_qq_1() : qqInfo.getFigureurl_qq_2();
Date curDate = new Date();
//上传头像到本地
user.setQqOpenId(openId);
user.setJoinTime(curDate);
user.setNickName(nickName);
user.setQqAvatar(avatar);
user.setUserId(StringTools.getRandomString(Constants.LENGTH_10));
user.setLastLoginTime(curDate);
user.setStatus(UserStatusEnum.ENABLE.getStatus());
user.setUseSpace(0L);
user.setTotalSpace(redisComponent.getSysSettingsDto().getUserInitUseSpace() * Constants.MB);
this.userInfoMapper.insert(user);
user = userInfoMapper.selectByQqOpenId(openId);
} else {
UserInfo updateInfo = new UserInfo();
updateInfo.setLastLoginTime(new Date());
avatar = user.getQqAvatar();
this.userInfoMapper.updateByQqOpenId(updateInfo, openId);
}
if (UserStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
throw new BusinessException("账号被禁用无法登录");
}
SessionWebUserDto sessionWebUserDto = new SessionWebUserDto();
sessionWebUserDto.setUserId(user.getUserId());
sessionWebUserDto.setNickName(user.getNickName());
sessionWebUserDto.setAvatar(avatar);
if (ArrayUtils.contains(appConfig.getAdminEmails().split(","), user.getEmail() == null ? "" : user.getEmail())) {
sessionWebUserDto.setAdmin(true);
} else {
sessionWebUserDto.setAdmin(false);
}
UserSpaceDto userSpaceDto = new UserSpaceDto();
userSpaceDto.setUseSpace(fileInfoService.getUserUseSpace(user.getUserId()));
userSpaceDto.setTotalSpace(user.getTotalSpace());
redisComponent.saveUserSpaceUse(user.getUserId(), userSpaceDto);
return sessionWebUserDto;
}
根据code,获取访问令牌
获取访问令牌的url,如:https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id={appId}&client_secret={appKey}&code={code}&redirect_uri={redirectUrl}
private String getQQAccessToken(String code) {
/**
* 返回结果是字符串 access_token=*&expires_in=7776000&refresh_token=* 返回错误 callback({UcWebConstants.VIEW_OBJ_RESULT_KEY:111,error_description:"error msg"})
*/
String accessToken = null;
String url = null;
try {
url = String.format(appConfig.getQqUrlAccessToken(), appConfig.getQqAppId(), appConfig.getQqAppKey(), code, URLEncoder.encode(appConfig.getQqUrlRedirect(), "utf-8"));
} catch (UnsupportedEncodingException e) {
logger.error("encode失败");
}
String tokenResult = OKHttpUtils.getRequest(url);
if (tokenResult == null || tokenResult.indexOf(Constants.VIEW_OBJ_RESULT_KEY) != -1) {
logger.error("获取qqToken失败:{}", tokenResult);
throw new BusinessException("获取qqToken失败");
}
String[] params = tokenResult.split("&");
if (params != null && params.length > 0) {
for (String p : params) {
if (p.indexOf("access_token") != -1) {
accessToken = p.split("=")[1];
break;
}
}
}
return accessToken;
}
获取openid
携带访问令牌,https://graph.qq.com/oauth2.0/me?access_token={accessToken}
private String getQQOpenId(String accessToken) throws BusinessException {
// 获取openId
String url = String.format(appConfig.getQqUrlOpenId(), accessToken);
String openIDResult = OKHttpUtils.getRequest(url);
String tmpJson = this.getQQResp(openIDResult);
if (tmpJson == null) {
logger.error("调qq接口获取openID失败:tmpJson{}", tmpJson);
throw new BusinessException("调qq接口获取openID失败");
}
Map jsonData = JsonUtils.convertJson2Obj(tmpJson, Map.class);
if (jsonData == null || jsonData.containsKey(Constants.VIEW_OBJ_RESULT_KEY)) {
logger.error("调qq接口获取openID失败:{}", jsonData);
throw new BusinessException("调qq接口获取openID失败");
}
return String.valueOf(jsonData.get("openid"));
}
获取qq用户信息
https://graph.qq.com/user/get_user_info?access_token={accessToken}&oauth_consumer_key=%s&openid={openid}
private QQInfoDto getQQUserInfo(String accessToken, String qqOpenId) throws BusinessException {
String url = String.format(appConfig.getQqUrlUserInfo(), accessToken, appConfig.getQqAppId(), qqOpenId);
String response = OKHttpUtils.getRequest(url);
if (StringUtils.isNotBlank(response)) {
QQInfoDto qqInfo = JsonUtils.convertJson2Obj(response, QQInfoDto.class);
if (qqInfo.getRet() != 0) {
logger.error("qqInfo:{}", response);
throw new BusinessException("调qq接口获取用户信息异常");
}
return qqInfo;
}
throw new BusinessException("调qq接口获取用户信息异常");
}
本地调试qq登录注意事项
效果
修改配置
作如下的配置,就可以在本地调试qq登录了,等调好了,再发布到公网
-
可以修改本地hosts文件(在:C:\Windows\System32\drivers\etc\目录下)
-
修改vue.config.js中devServer的配置,添加host为all(否则,访问时会出现
Invalid Host Header
)const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { port: 80, allowedHosts:'all', // 添加这个 } })
登录前端代码
绑定qq按钮点击事件
<template>
<span class="iconfont icon-qq" @click="jumpToQq"></span>
</template>
<script>
...
jumpToQq() {
getQQAuthUrl().then(({authUrl,state})=>{
location.href = authUrl
// 记录state
sessionStorage.setItem("state",state)
// 记录跳转之前所在的页面
sessionStorage.setItem(state,JSON.stringify({fullPath:this.$route.fullPath}))
})
}
...
</script>
路由配置
const routes = [
...
{
path:'/oauth/login/qq',
component: ()=> import(`@/views/oauth/QQLogin.vue`)
}
...
]
QQLogin.vue
当qq授权页面通过时,qq会让浏览器重定向到redirectUri的页面,就是此页面,在此页面中,获取到地址栏路径上的参数,将此参数(授权码)传给后台,后台拿到这个授权码,就可以请求qq后台,获取用户的信息了,然后做登录即可
<template>
<div class="qqCover">
qq登录中,请勿刷新页面...
</div>
</template>
<script>
import {getQQUserInfo} from '@/api/loginApi'
export default {
name: 'qqLogin',
mounted() {
console.log(this.$route,'当前路由');
getQQUserInfo({code:this.$route.query.code}).then(token=>{
// console.log('token',token);
this.$store.dispatch('user/doQQLogin',{token}).then(res=>{
console.log('router--',res,this.$router);
let state = sessionStorage.getItem('state')
let fullPath = JSON.parse(sessionStorage.getItem(state))
// console.log('fullPath', fullPath);
sessionStorage.removeItem('state')
sessionStorage.removeItem(state)
if(fullPath) {
this.$router.push(fullPath.fullPath)
} else {
this.$router.push('/')
}
})
})
},
components: {
}
}
</script>
<style lang="scss">
.qqCover {
position: fixed;
width: 100%;
height: 100%;
background-color: #fff;
z-index: 999;
}
</style>
user.js
import storage from '@/utils/storage'
import axiosInstance from '@/utils/request'
export default {
namespaced: true,
state: {
token: localStorage.getItem('token') || '',
userInfo: storage.get('userInfo')
},
mutations: {
SET_TOKEN(state, token) {
state.token = token
},
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo
}
},
actions: {
clearUserInfo({commit}){
commit('SET_TOKEN', '')
commit('SET_USER_INFO', null)
localStorage.removeItem('token')
storage.removeKey('userInfo')
console.log('clearUserInfo');
},
getUserInfo({ commit }) {
// 获取用户信息
return new Promise(async (resolve, reject) => {
let userInfo = await axiosInstance({
url: '/admin/user/getCurrUserInfo',
method: 'post',
})
commit('SET_USER_INFO', userInfo)
storage.set('userInfo', userInfo)
resolve()
})
},
doLogin({ commit, dispatch }, payload) {
return new Promise(async (resolve, reject) => {
try {
let formData = new FormData()
const { username, password } = payload
formData.append("username", username)
formData.append("password", password)
// 登录
let token = await axiosInstance({
url: '/login',
method: 'Post',
data: formData,
})
commit('SET_TOKEN', token)
localStorage.setItem('token', token)
await dispatch('getUserInfo')
resolve()
} catch (error) {
reject(error)
}
})
},
doQQLogin({commit,dispatch},payload){
return new Promise(async (resolve,reject) => {
try {
const {token} = payload
commit('SET_TOKEN', token)
localStorage.setItem('token', token)
await dispatch('getUserInfo')
resolve()
} catch (error) {
reject()
}
})
},
doLogout({commit}) {
return new Promise(async (resolve, reject) => {
await axiosInstance({
url: '/logout',
method: 'Post',
})
commit('SET_TOKEN', '')
commit('SET_USER_INFO', null)
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
resolve()
})
}
},
getters: {
}
}
后台代码
OauthLoginController
@Api(tags = "第三方登录")
@RestController
public class OauthLoginController {
@Autowired
private OauthService oauthService;
@ApiOperation("获取qq授权url")
@PostMapping("qq/getQQAuthUrl")
public Result<Map<String,Object>> getQQAuthUrl() {
return Result.ok(oauthService.getQQAuthUrl());
}
@ApiOperation("获取qq用户信息")
@GetMapping("qq/getQQUserInfo")
public void getUserInfo(@RequestParam("code") String code) {
oauthService.getQQUserInfo(code);
}
}
OauthServiceImpl
@Slf4j
@Service
public class OauthServiceImpl implements OauthService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private QQProperties qqProperties;
@Autowired
private TokenSessionAuthenticationStrategy tokenSessionAuthenticationStrategy;
@Autowired
private IUserAuthService userAuthService;
@Autowired
private IUserInfoService userInfoService;
@Autowired
private IUserRoleService userRoleService;
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private HttpServletRequest request;
@Autowired
@SuppressWarnings("all")
private HttpServletResponse response;
@Override
public Map<String,Object> getQQAuthUrl() {
// 生成的state给前端使用
String state = UUID.randomUUID().toString().replaceAll("-", "");
String authorizationUrl = UriComponentsBuilder.fromHttpUrl(QQProperties.AUTHORIZATION_URL)
.queryParam("response_type", "code")
.queryParam("client_id", qqProperties.getAppId())
.queryParam("redirect_uri", qqProperties.getRedirectUri())
.queryParam("state", state)
.build().toUriString();
return MapBuilder.newHashMap()
.put("state", state)
.put("authUrl", authorizationUrl)
.build();
}
@Override
@Transactional
public void getQQUserInfo(String code) {
// 获取访问令牌
String accessToken = getQQAccessToken(code);
// 获取qq用户openid
String openid = getQQOpenid(accessToken);
UserAuthEntity existingUserAuthEntity = userAuthService.getOne(new QueryWrapper<UserAuthEntity>()
.lambda()
.eq(UserAuthEntity::getUsername, openid)
.eq(UserAuthEntity::getLoginType, LoginTypeEnum.QQ_LOGIN.loginType())
);
if (existingUserAuthEntity == null) {
// 获取qq用户信息
URI uri = UriComponentsBuilder.fromHttpUrl(QQProperties.GET_USER_INFO)
.queryParam("access_token", accessToken)
.queryParam("oauth_consumer_key", qqProperties.getAppId())
.queryParam("openid", openid)
.build()
.toUri();
String userInfoJson = restTemplate.getForObject(uri, String.class);
log.info("userInfoJson -> {}", userInfoJson);
Map<String,String> data = JsonUtil.json2Obj(userInfoJson, Map.class);
UserInfoEntity userInfoEntity = new UserInfoEntity();
userInfoEntity.setAvatar(data.get("figureurl_qq"));
userInfoEntity.setNickname(data.get("nickname"));
userInfoEntity.setDisabled(SysConstants.AVAILABLE);
userInfoService.save(userInfoEntity);
UserAuthEntity userAuthEntity = new UserAuthEntity();
userAuthEntity.setUsername(openid);
userAuthEntity.setPassword(accessToken);
userAuthEntity.setLoginType(LoginTypeEnum.QQ_LOGIN.loginType());
userAuthEntity.setUserInfoId(userInfoEntity.getId());
userAuthService.save(userAuthEntity);
UserRoleEntity userRoleEntity = new UserRoleEntity();
userRoleEntity.setRoleId(SysConstants.DEFAULT_USER_ROLE_ID);
userRoleEntity.setUserInfoId(userInfoEntity.getId());
userRoleService.save(userRoleEntity);
existingUserAuthEntity = userAuthEntity;
}
// 用户信息 和 加载用户权限
UserDetailDTO userDetailDTO = new UserDetailDTO();
UserInfoEntity userInfoEntity = userInfoService.getOne(new QueryWrapper<UserInfoEntity>()
.lambda()
.eq(UserInfoEntity::getId, existingUserAuthEntity.getUserInfoId())
);
BeanUtil.copyBeanProps(existingUserAuthEntity, userDetailDTO);
BeanUtil.copyBeanProps(userInfoEntity, userDetailDTO);
userDetailDTO.setUserInfoId(userInfoEntity.getId());
userDetailDTO.setUserAuthId(existingUserAuthEntity.getId());
userDetailDTO.setLoginType(LoginTypeEnum.QQ_LOGIN.loginType());
userDetailDTO.setLoginTime(new Date());
parseLoginInfo(userDetailDTO);
UserPermDTO userPermDTO = userInfoMapper.listRoleAndPermsForUser(userInfoEntity.getId());
userDetailDTO.setPerms(userPermDTO.getPerms().stream().filter(p->!StringUtils.isEmpty(p)).collect(Collectors.toList()));
userDetailDTO.setRoles(userPermDTO.getRoles().stream().filter(r -> !StringUtils.isEmpty(r)).collect(Collectors.toList()));
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetailDTO, "N/A", userDetailDTO.getAuthorities());
tokenSessionAuthenticationStrategy.onAuthentication(usernamePasswordAuthenticationToken, request, response);
}
private void parseLoginInfo(UserDetailDTO userDetailDTO) {
String ipAddress = IpUtil.getIpAddress(request);
String ipSource = IpUtil.getIpSource(ipAddress);
UserAgent userAgent = IpUtil.getUserAgent(request);
OperatingSystem os = userAgent.getOperatingSystem();
userDetailDTO.setIpAddress(ipAddress);
userDetailDTO.setIpSource(ipSource);
userDetailDTO.setOsName(os.getName());
userDetailDTO.setBrowserName(userAgent.getBrowser().getName());
}
private String getQQOpenid(String accessToken) {
URI uri = UriComponentsBuilder.fromHttpUrl(QQProperties.GET_OPENID_URL)
.queryParam("access_token", accessToken)
.queryParam("fmt", "json")
.build()
.toUri();
String result = restTemplate.getForObject(uri, String.class);
Map<String,Object> data = JsonUtil.json2Obj(result, Map.class);
Object openid= data.get("openid");
if (openid == null) {
throw BizException.QQ_LOGIN_ERR;
}
return String.valueOf(openid);
}
private String getQQAccessToken(String code) {
URI uri = UriComponentsBuilder.fromHttpUrl(QQProperties.ACCESS_TOKEN_URL)
.queryParam("grant_type", "authorization_code")
.queryParam("client_id", qqProperties.getAppId())
.queryParam("client_secret", qqProperties.getAppKey())
.queryParam("code", code)
.queryParam("redirect_uri", qqProperties.getRedirectUri())
.queryParam("fmt", "json")
.build()
.toUri();
String result = restTemplate.getForObject(uri, String.class);
Map<String,Object> data = JsonUtil.json2Obj(result, Map.class);
Object access_token = data.get("access_token");
if (access_token == null) {
throw BizException.QQ_LOGIN_ERR;
}
return String.valueOf(access_token);
}
}
QQProperties
@Data
@Component
@ConfigurationProperties(prefix = "qq")
public class QQProperties {
// 跳转qq授权页面
// 需要携带 response_type=code client_id redirect_uri state
public static final String AUTHORIZATION_URL = "https://graph.qq.com/oauth2.0/authorize";
// 获取访问令牌
// 需要携带: grant_type=grant_type client_id client_secret code fmt=json
public static final String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
// 获取授权用户的openid
// 需要携带: access_token fmt=json
public static final String GET_OPENID_URL = "https://graph.qq.com/oauth2.0/me";
// 获取授权用户信息
// 需要携带: access_token oauth_consumer_key openid
public static final String GET_USER_INFO = "https://graph.qq.com/user/get_user_info";
private String appId;
private String appKey;
private String redirectUri;
}
application.yml
qq:
appId: 填入自己在qq互联申请的appId
appKey: 填入自己在qq互联申请的appKey
redirectUri: http://www.pscool.fun/oauth/login/qq
坑
有几个踩了,可以注意一下:
- 按上面的配置后,一定要把要把自己本地电脑的vpn软件给关闭掉(没开就算了)
- chrome浏览器居然没有报跨域异常,而Axios则是报了 NETWORD ERROR的错误,导致我在控制台的network里面,看到的响应状态码是200,但是又看不到响应数据,后台又的的确确执行了代码。后来换成了Edge浏览器,才看到的是跨域错误