博客之QQ登录功能(一)
流程图
上图spring social 封装了1-8步需要的工作
1、新建包和书写配置文件
public class QQProperties {//App唯一标 识private String appId = "100550231";private String appSecret = "69b6ab57b22f3c2fe6a6149274e3295e";//QQ供应商private String providerId = "callback.do";//拦截器拦截的请求private String filterProcessesUrl = "/qqLogin";//get set 方法//...
}
@ConfigurationProperties(prefix = "blog.security")
public class BlogSecurityProperties {private QQProperties qqProperties = new QQProperties();//get set...
}
@Configuration
//让我们的配置生效
@EnableConfigurationProperties (BlogSecurityProperties.class)
public class BlogSecurityConfig {}
2、获取QQ用户信息
package com.zzz.blog.social.qq.api;public interface QQ {//返回一个QQ的用户信息QQUserInfo getUserInfo();
}
package com.zzz.blog.social.qq.api;import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";//外界赋值private String appId;//用户的唯一 标识,urlprivate String openId;private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic QQUserInfo getUserInfo() {// TODO Auto-generated method stubreturn null;}}
package com.zzz.blog.social.qq.api;public class QQUserInfo {private String is_lost;private String province;private String city;private String year;private String constellation;private String ret;private String msg;private String nickname;private String figureurl;private String figureurl_1;private String figureurl_2;private String figureurl_qq_1;private String figureurl_qq_2;private String figureurl_qq;private String figureurl_type;private String gender_type;private String gender;private String is_yellow_vip;private String vip;private String yellow_vip_level;private String level;private String is_yellow_year_vip;private String openId;//get/set...
}
修改代码:
//获取用户信息@Overridepublic QQUserInfo getUserInfo() {//拼接参数String url = String.format(URL_GET_USERINFO, appId, openId);//发送请求String result = getRestTemplate().getForObject(url, String.class);//处理返回值QQUserInfo userInfo = null;try {userInfo = objectMapper.readValue(result, QQUserInfo.class);userInfo. setOpenId(openId);} catch (JsonProcessingException e) {throw new RuntimeException(" 获取用户信息失败!");}return userInfo;}
3、如何获得OpenId以及AppId
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?pauth_consumer_key=%s&openid=%s";private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";//外界赋值private String appId;//用户的唯一 标识,urlprivate String openId;private ObjectMapper objectMapper = new ObjectMapper();public QQImpl(String accessToken, String appId) {//自动拼接一个参数super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER) ;//赋值appid this.appId = appId;//赋值openid//通过url获得openid//拼接参数String url = String.format(URL_GET_OPENID, accessToken);//发送请求String result = getRestTemplate().getForObject(url, String.class);//处理返回值//callback( {"client_ id":"100550231", ”openid":"CDF1A28F8698E326D173DE17437FB098"} );result = StringUtils.replace(result, "callback( ","");result = StringUtils.replace(result, " );","");//{"client_ id": "100550231","openid": "CDF1A28F8698E326D173DE17437FB098"}OpenId id = null;try {id = objectMapper.readValue(result, OpenId.class);} catch (JsonProcessingException e) {throw new RuntimeException( "获取OpenId失败! ! ");}//赋值openidthis.openId = id.getOpenid();}//获取用户信息@Overridepublic QQUserInfo getUserInfo() {...}}
package com.zzz.blog.social.qq.api;public class OpenId {private String client_id;private String openid;//get/set
}
4、完成QQOAuth2Template
package com.zzz.blog.social.qq.template;import ...public class QQOAuth2Template extends OAuth2Template{public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {super(clientId, clientSecret, authorizeUrl, accessTokenUrl);//使clientId、clientSecret可以拼接到一起setUseParametersForClientAuthentication(true);}//添加text/html@Overrideprotected RestTemplate createRestTemplate() {RestTemplate template = super.createRestTemplate();template.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));return template;}//把请求的格式按照qq的标准,做了一些自定义信息 自己处理请求 按&分割字符,分割后如下//access_token=FE04***** *****************CCE2 items[0]//expires_in=7776000 item[0]//refresh_token=88E4********* **************BE14 item[1]@Overrideprotected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);//StringUtils.split只切割了一次,坑String[] items = StringUtils.split(responseStr, "&");String[] item = StringUtils.split(items[1], "&");String access_token =StringUtils.replace(items[0], "access_token=", "");Long expires_in = new Long(StringUtils.replace(item[0], "expires_in=", ""));String refresh_token = StringUtils.replace(item[1], "refresh_token=", "");return new AccessGrant(access_token, null, refresh_token, expires_in);}}
5、完成QQAdapter与ServiceProvider
package com.zzz.blog.social.qq.connection;import ...public class QQAdapter implements ApiAdapter<QQ>{@Overridepublic boolean test(QQ api) {// 始终为truereturn true;}@Overridepublic void setConnectionValues(QQ api, ConnectionValues values) {//获取userinfoQQUserInfo userInfo = api. getUserInfo();//获取用户名称values.setDisplayName(userInfo.getNickname());//获取头像values.setImageUrl(userInfo.getFigureurl_qq_1());//获取个人主页values.setProfileUrl(null);//openid,用户在服务商中的唯一标识values.setProviderUserId(userInfo.getOpenId());}@Overridepublic UserProfile fetchUserProfile(QQ api) {// TODO Auto-generated method stubreturn null;}@Overridepublic void updateStatus(QQ api, String message) {// TODO Auto-generated method stub}}
package com.zzz.blog.social.qq.connection;import ...public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ>{//将用户导向认证服务器中的ur1地址,用户在该地址上进行授权private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";//在获取令牌的时候,需要访问的urlprivate static final String URL_ACCESSTOEKN = "https://graph.qq.com/oauth2.0/token";private String appId;//1-6public QQServiceProvider(String appId,String appSecret) {super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESSTOEKN));this.appId = appId;}//7-8@Overridepublic QQ getApi(String accessToken) {// TODO Auto-generated method stubreturn new QQImpl(accessToken, appId);}}
6、完成QQConfig与ConnectionFactory
package com.zzz.blog.social.qq.connection;import ...public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ>{public QQConnectionFactory(String providerId, String appId,String appSecret) {super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());}}
package com.zzz.blog.social.qq.config;import ...@Configuration
@EnableSocial
@Order(2)
public class QQConfig extends SocialConfigurerAdapter{@Autowiredprivate BlogSecurityProperties blogSecurityProperties;//添加qq创建connection的工厂@Overridepublic void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer,Environment environment) {QQProperties qqConfig = blogSecurityProperties.getQqProperties();QQConnectionFactory qqConnectionFactory = new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());connectionFactoryConfigurer.addConnectionFactory(qqConnectionFactory);}//获取登陆人@Overridepublic UserIdSource getUserIdSource() {return new AuthenticationNameUserIdSource();}}
7、创建表以及创建操作表的类JdbcUsersConnectionRepository
package com.zzz.blog.social.qq.config;import ...@Configuration
@EnableSocial
@Order(1)
public class SocialConfig extends SocialConfigurerAdapter{@Autowiredprivate DataSource dataSource;//登录之后,直接将QQ的数据保存在数据库@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());return repository;} //改变拦截的请求//在注册的过程中,拿到了这个SpringSocial中的信息//业务完成之后,把用户的id传给了SpringSocial//打开ConnectController
}
找到socailJDBC表格式,在数据库中执行sql
8、改变拦截的请求
package com.zzz.blog.social.qq.config;import ...public class ZZZSpringSocialConfigurer extends SpringSocialConfigurer{private String filterProcessesUrl;public ZZZSpringSocialConfigurer(String filterProcessesUrl) {this.filterProcessesUrl = filterProcessesUrl;}//将默认的拦截改为qqLogin@Overrideprotected <T> T postProcess(T object) {//获得filterSocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);//设置字段filter.setFilterProcessesUrl(filterProcessesUrl);return (T) filter;}}
//改变拦截的请求 /auth -> /qqLogin@Beanpublic SpringSocialConfigurer zzzSocialSecurityConfig() {String filterProcessesUrl = blogSecurityProperties.getQqProperties().getFilterProcessesUrl();ZZZSpringSocialConfigurer zzzSpringSocialConfigurer = new ZZZSpringSocialConfigurer(filterProcessesUrl);return zzzSpringSocialConfigurer;}
9、将Social中的配置生效到SpringSecurity中
在SocialConfig类中添加代码
//在注册的过程中,拿到了这个SpringSocial中的信息//业务完成之后,把用户的id传给了SpringSocial@Beanpublic ProviderSignInUtils providerSignInUtils() {return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));}//打开ConnectController@Beanpublic ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,ConnectionRepository connectionRepository) {return new ConnectController(connectionFactoryLocator, connectionRepository);}
添加apply配置socialconfig
package com.zzz.blog.config;import ...//安全配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{//SpringSecurity加密方法返回值@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Autowiredprivate SpringSocialConfigurer zzzSocialSecurityConfig;//做拦截@Overrideprotected void configure(HttpSecurity http) throws Exception {// 请求授权http.formLogin().and().authorizeRequests()//授权放行.antMatchers("/*.html").permitAll()//所有请求.anyRequest()//都需要身份认证.authenticated().and()//43、使用Layer打开select-mood子页面并配置SpringSecurity允许Iframe嵌入页面 .headers().frameOptions().disable().and()//跨站请求伪造的防护.csrf().disable()//添加我们所写的spring social配置.apply(zzzSocialSecurityConfig);}}
10、创建Visitor实体并实现SocialUserDetailsService接口查找Visitor
package com.zzz.blog.domain;import ...@Entity
public class Visitor {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;private String image;protected Visitor() {}public Visitor(Long id, String username, String password, String image) {super();this.id = id;this.username = username;this.password = password;this.image = image;}//get/set
}
package com.zzz.blog.repository;import ...public interface VisitorRepository extends CrudRepository<Visitor, Long>{}
@Component
public class SocialVisitorServiceImpl implements SocialUserDetailsService{@Autowiredprivate VisitorRepository visitorRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {//根据userId查找访客Optional<Visitor> optional = visitorRepository.findById(new Long(userId));Visitor visitor = optional.get();if (visitor == null) {throw new UsernameNotFoundException(userId);}return new SocialUser(visitor.getUsername(), passwordEncoder.encode(visitor.getPassword()), true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("VISITOR"));}}
11、实现ConnectionSignUp接口添加Visitor
package com.zzz.blog.social.qq.signup;import ...@Component
public class DemoConnectionSignUp implements ConnectionSignUp{@Autowiredprivate VisitorService visitorService;//根据社交用户的信息,创建一个Visitor并返回唯一标识@Overridepublic String execute(Connection<?> connection) {Visitor visitor = new Visitor(null, connection.getDisplayName(), "123456", connection.getImageUrl());visitor = visitorService.saveVisitory(visitor);return visitor.getId().toString();}}
package com.zzz.blog.service;import ...@Service
public interface VisitorService {Visitor saveVisitory(Visitor visitor);}
package com.zzz.blog.service;import ...@Service
public interface VisitorService {Visitor saveVisitory(Visitor visitor);}
package com.zzz.blog.service;import ...@Component
public class VisitorServiceImpl implements VisitorService{@Autowiredprivate VisitorRepository visitorRepository;@Overridepublic Visitor saveVisitory(Visitor visitor) {return visitorRepository.save(visitor);}}
SocialConfig添加setConnectionSignUp执行方法
@Autowiredprivate ConnectionSignUp connectionSignUp;//登录之后,直接将QQ的数据保存在数据库@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());repository.setConnectionSignUp(connectionSignUp);return repository;}
12、测试QQ登录
application.properties添加代码如下(修改端口):
server.port=80
login.html修改超链接代码如下:
<a href="/qqLogin/callback.do" class="login100-social-item bg2"><i class="fa fa-qq"></i></a>
修改C:\Windows\System32\drivers\etc\hosts文件
127.0.0.1 www.pinzhi365.com
这是别人提供的测试地址。我们也可以到QQ互联官网https://connect.qq.com/上注册用户,创建应用。
其中回调地址的写法:网站地址/拦截器拦截的路径/服务提供商。
创建完修改QQProperties类上的对应配置即可。
测试通过,控制台打印了添加visitor数据的sql。
相关文章:

博客之QQ登录功能(一)
流程图 上图spring social 封装了1-8步需要的工作 1、新建包和书写配置文件 public class QQProperties {//App唯一标 识private String appId "100550231";private String appSecret "69b6ab57b22f3c2fe6a6149274e3295e";//QQ供应商private String…...

Redis多机数据库实现
Redis多机数据库实现 为《Redis设计与实现》笔记 复制 客户端可以使用SLAVEOF命令将指定服务器设置为该服务器的主服务器 127.0.0.1:12345> SLAVEOF 127.0.0.1 6379127.0.0.1:6379将被设置为127.0.0.1:123456的主服务器 旧版复制功能的实现 Redis的复制功能分为同步&a…...

Leangoo领歌 -敏捷任务管理软件,任务管理更轻松更透明
任务管理,简单易懂,就是对任务进行管理。那怎么可以更好进行任务管理呢?怎么样样可以让任务进度可视化,一目了然呢?有效的管理可以让我们事半功倍。 接下来我们看一下如何借助任务管理软件高效的做任务管理。 首先…...

go的iris框架进行本地资源映射到服务端
我这里使用的是HandleDirapi,有其他的请补充 package mainimport ("github.com/kataras/iris/v12" )type Hello struct{Status int json:"status"Message string json:"message" }func main(){app : iris.New()//第一个api:相当于首页app.Get(&q…...
代码随想录day46|139. 单词拆分
139. 单词拆分 class Solution:def wordBreak(self, s: str, wordDict: List[str]) -> bool:dp [False]*(len(s)1)dp[0]Truefor i in range(len(s)1):for j in wordDict:if i>len(j) and (s[i-len(j):i] in wordDict) and dp[i-len(j)]:dp[i] Truereturn dp[len(s)]多…...

MATLAB实现函数拟合
目录 一.理论知识 1.拟合与插值的区别 2.几何意义 3.误差分析 二.操作实现 1.数据准备 2.使用cftool——拟合工具箱 三.函数拟合典例 四.代码扩展 一.理论知识 1.拟合与插值的区别 通俗的说,插值的本质是根据现有离散点的信息创建出更多的离散点…...

vue优化首屏加载时间优化-cdn引入第三方包
前言 为什么要进行首屏加载优化,因为随着我们静态资源和第三方包和代码增加,压缩之后包会越来越大 随着网络的影响,在我们第一输入url请求资源时候,网络阻塞,加载时间长,用户体验不好 仔细观察后就会发现…...
lv4 嵌入式开发-3 标准IO的读写
目录 1 标准I/O – 读写流 2 标准I/O – 按字符输入 3 标准I/O – 按字符输出 4 标准I/O – 思考和练习 5 标准I/O – 按行输入 6 标准I/O – 按行输出 7 标准I/O – 思考和练习 1 标准I/O – 读写流 流支持不同的读写方式: 读写一个字符:fgetc()/fputc()一…...
iOS UIDevice设备信息
识别设备和操作系统 //获得共享设备实例 open class var current: UIDevice { get }//识别设备的名称 open var name: String { get } // e.g. "My iPhone"//设备类型 open var model: String { get } // e.g. "iPhone", "iPod touch"//本地化设…...
SLAM ORB-SLAM2(2)编译安装
SLAM ORB-SLAM2(2)编译安装 1. 软件包依赖安装2. 依赖安装2.1. Eigen2.2. Pangolin2.3. OpenCV3. ORB-SLAM23.1. 源码下载3.2. 文件修改3.3. 扩大交换空间3.4. 编译1. 软件包依赖安装 以一个纯净的ubuntu20.04桌面版为例 1.首先设置软件源为清华源 2.安装必要依赖 sudo ap…...
第11节-PhotoShop基础课程-索套工具
文章目录 前言1.索套工具 选中后按Ctrl 可以移动2.加,减,交叉 shift alt 2.多边形索套工具 手动首尾相连 或者双击空地1.单击绘制直线选区2.双击结束绘制3.加,减,交叉4. delete可以删除节点 3.磁性索套工具1.沿着边缘自动吸附2.可…...

Json字符串内容比较-超实用版
背景 之前有类似接口diff对比,数据对比的测试需求,涉及到json格式的数据对比,调研了几个大神们分享的代码,选了一个最符合自己需求的研究了下。 说明 这个对比方法,支持JsonObject和JsonArray类型的数据对比&#x…...
Redis系列之客户端Redisson
概述 官方推荐的客户端,支持Redis单实例、Redis哨兵、Redis Cluster、Redis master-slave等各种部署架构。 GitHub, 功能: 分布式锁 分布式锁 使用Redisson提供的分布式锁的一个最常见场景,应用部署为多个节点,然…...

centos 端口被占用的快速排查方式
问题笔记 centos 端口被占用的快速排查方式 centos 端口被占用的快速排查方式 这里说一个我刚刚遇到的问题,解决步骤用来记录,方便以后自己查询。 nginx配置完index.html测试文件,发现一直显示的404页面。 我跑到服务器上想重启一下nginx …...

Java“牵手”淘宝商品列表数据,关键词搜索淘宝商品数据接口,淘宝API申请指南
淘宝商城是一个网上购物平台,售卖各类商品,包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取淘宝商品列表和商品详情页面数据,您可以通过开放平台的接口或者直接访问淘宝商城的网页来获取商品详情信息。以下是两种常用方法的介绍&…...
OpenEuler/CentOS如何修改密码策略
密码策略文件: /etc/pam.d/system-auth 找到行: password requisite pam_pwquality.so try_first_pass local_users_only 为保证安全,可以将这一行注释掉,添加一行,最后结果如下: #password …...

# Spring MVC与RESTful API:如何设计高效的Web接口
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...

Scrum敏捷模式的优势点、实践经验及适用企业
Scrum敏捷模式是一种灵活、适应性强的开发方法,其核心理念是以短周期、高频率的方式进行项目开发,确保团队能够快速响应变化。 Scrum包含三个角色:产品负责人(Product Owner)、Scrum Master和开发团队(Tea…...

【C++杂货铺】探索stack和queue的底层实现
文章目录 一、stack的介绍和使用1.1 stack的介绍1.2 stack的使用1.2.1 最小栈1.2.2 栈的压入、弹出序列1.2.3 逆波兰表达式求值1.2.4 用栈实现队列 二、queue的介绍和使用2.1 queue的介绍2.2 queue的使用2.2.1 二叉树的层序遍历 三、模拟实现3.1 stack模拟实现3.2 queue模拟实现…...

“系统的UI”——SystemUI
SystemUI的实现 以StatusBar为例,来分析下Android系统具体是如何实现它们的。 相关代码分为两部分,即: Service部分 代码路径:frameworks/base/services/java/com/android/server。 应用部分 代码路径:frameworks…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...