easypan部署 & qq登录

项目部署

学习链接

Springboot vue3 仿百度网盘

linux下安装ffmpeg的详细教程 - 完全参照这个安装的
FFmpeg视频处理入门教程----从安装到使用(Linux版)
linux下ffmpeg安装教程(小学生都能看懂)

【java使用ffmpeg进行视频压缩】

文件上传java报Processing of multipart/form-data request failed. java.io.EOFException: Unexpected EOF read

Java使用FFmpeg处理视频文件指南

第三方登录入口(QQ)

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互联

在这里插入图片描述
步骤:

  1. 浏览器点击QQ登陆按钮,服务器返回QQ登陆网址(手机扫码界面)。
  2. 用户进行QQ登陆,登陆成功重定向服务器callback网址,并携带授权code与state,state就是next参数,用于登陆成功后返回的页面。
  3. 向服务器访问callback?code=xxx&state=xxx,凭借code向QQ服务器请求access token,然后QQ服务器返回access token给服务器,然后服务器凭借着access token向QQ服务器请求用户的openid,再返回用户的openid(用户的唯一身份表示)。
  4. 下面判断用户是否第一次使用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
  • 下面有一个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浏览器,才看到的是跨域错误
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
easypan云盘项目是一个基于云技术的文件存储和分享平台。用户可以通过easypan云盘将个人和工作文件上传至云端,随时随地访问和管理文件,实现数据共享和备份。该项目旨在提供简单易用、安全可靠的云存储解决方案。 easypan云盘的主要特点有以下几个方面:首先,用户可以通过简单的界面操作快速上传和下载文件。只需拖放文件到云盘界面,文件即可自动上传,并生成链接供他人访问。其次,easypan云盘采用多重加密技术,确保用户文件的安全性。所有数据传输和存储过程都经过严格的加密,用户不必担心文件泄漏的风险。此外,easypan云盘还提供了权限管理功能,用户可以设置不同的访问权限,有效控制文件的分享范围。 除了文件存储和分享功能外,easypan云盘还提供了文件同步和备份服务。用户可以选择将电脑上的文件夹和easypan云盘中的文件夹进行同步,确保数据的实时备份和跨设备的访问。无论是在电脑、手机还是平板电脑上,用户都可以随时随地浏览和管理文件。 易用性也是easypan云盘项目的一大特点。用户无需进行繁琐的安装和配置,只需在浏览器中访问easypan云盘网站,即可开始使用。同时,easypan云盘支持多种常见的文件格式,无论是文档、图片、音频还是视频文件,都能够轻松地在云盘中打开和预览。 综上所述,easypan云盘项目提供了便捷、安全和可靠的文件存储和分享解决方案,满足了用户的个人和工作需求。无论是个人用户还是企业用户,都可以通过easypan云盘轻松管理和共享文件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值