微信公众号开发—通过网页授权实现业务系统登录及用户绑定(微信网页授权自动登录业务系统)

😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 微信公众号开发—通过网页授权实现业务系统登录及用户绑定(微信网页授权自动登录业务系统)
⏱️ @ 创作时间: 2022年12月19日

目录

  • 1、准备工作
  • 2、登录授权绑定说明
  • 3、登录授权绑定流程
  • 4、基础代码实现
    • 4.1 定义工具类
    • 4.2 模拟前端获取Code
    • 4.3 授权绑定操作接口

1、准备工作

1、在本地进行联调时,为让微信端能够访问到本地服务,需要进行内网穿透,参考《本地服务器内网穿透实现(NATAPP)》
2、配置网页授权获取用户基本信息,用于告诉微信发起授权的后端服务器地址

  • 正式公众号:在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”进行配置操作;
  • 测试沙箱环境:在 测试环境 中,进行配置网页授权
    在这里插入图片描述
    在这里插入图片描述

2、登录授权绑定说明

业务系统在外部浏览器默认情况通过手机号进行登录;在微信环境中,除了可以使用手机号登录外,也可以使用微信授权实现业务系统的登录;为了实现微信网页授权登录业务系统与手机号登录业务系统是统一的用户,需要在第一次使用微信授权登录时进行绑定操作。为了简单的记录是否存在绑定关系,此Demo为了简便演示操作,在后台数据库中通过表中的openId是否存在已表示是否存在绑定,表结构如下:

CREATE TABLE `tb_user_info` (`id` bigint NOT NULL COMMENT '主键',`mobile` varchar(12) DEFAULT NULL COMMENT '手机号',`opend_id` varchar(100) DEFAULT NULL COMMENT '公众号openId',`gender` int DEFAULT NULL COMMENT '性别',`nick_name` varchar(32) DEFAULT NULL COMMENT '昵称',`head_image` varchar(256) DEFAULT NULL COMMENT '头像链接',PRIMARY KEY (`id`)
)

3、登录授权绑定流程

  • 用户点击微信登录
    • 前端请求微信网页授权接口,询问用户是否同意授权;
    • 用户同意授权后,通过获取code;
    • 前端通过code调用后端wxLogin接口获取openId等微信用户信息;
    • 判断openId是否存在绑定关系(是否在数据表中)
      • 存在绑定关系,表示可以直接登录,返回用户信息及登录验证的token信息;
      • 不存在绑定关系,则判断前端调用wxLogin接口时,用户是否已经登录(token是否存在或有效),如果已登录则直接绑定openId并且返回绑定成功标识;如果没有登录,将通过openId获取的微信信息存入redis中,将redisKey返回到前端;
  • 前端检测是否存在redisKey(是否引导授权绑定)
    • 不存在redisKey,并且授权绑定成功,直接渲染当前用户信息;
    • 存在redisKey,进入授权绑定页面,引导用户输入手机号及验证码;
      • 前端将用户手机号、验证码、redisKey传入后端;
      • 后端判断redisKey是否过期(默认1小时过期),如果过期则要求用户重新点击微信登录;
      • 验证手机号及验证码是否正确,如果存在则要求用户重新填写;
      • 如果redisKey、手机号、验证码都正确,则判断该手机号是否存在于业务系统中;
      • 如果手机号存在于系统中,并且已经绑定了openId,则提示用户无法绑定;如果存在于系统中,但是没有绑定openId,则更新数据库,实现openId绑定;
      • 如果手机号不存在于系统中,则将手机号、openId、redisKey中的基本信息插入到数据库中,实现用户的注册已经openId绑定;

基础流程图:
在这里插入图片描述

4、基础代码实现

注意:Demo只实现,用户发起微信登录接口、授权绑定接口两个接口;获取验证码、验证码校验等流程只做伪代码描述,当前Demo没有做前端代码,由后端来模拟网页授权及授权回调后,获取code的接口;

4.1 定义工具类

MapUtils:

public class MapUtils {/*** Map转换为 Entity** @param params 包含参数的Map* @param t      需要赋值的实体* @param <T>    类型*/public static <T> T mapToEntity(Map<String, Object> params, T t) {if (null == params) {return t;}Class<?> clazz = t.getClass();Field[] declaredFields = clazz.getDeclaredFields();try {for (Field declaredField : declaredFields) {declaredField.setAccessible(true);String name = declaredField.getName();if (null != params.get(name)) {declaredField.set(t, params.get(name));}}} catch (Exception e) {throw new RuntimeException("属性设置失败!");}return t;}/*** 将对象转换为HashMap** @param t   转换为Map的对象* @param <T> 转换为Map的类* @return Map*/public static <T> Map<String, Object> entityToMap(T t) {Class<?> clazz = t.getClass();List<Field> allField = getAllField(clazz);Map<String, Object> hashMap = new LinkedHashMap<>(allField.size());try {for (Field declaredField : allField) {declaredField.setAccessible(true);Object o = declaredField.get(t);if (null != o) {hashMap.put(declaredField.getName(), o);}}} catch (Exception e) {throw new RuntimeException("属性获取失败!");}return hashMap;}/*** 获取所有属性** @param clazz class* @param <T>   泛型* @return List<Field>*/public static <T> List<Field> getAllField(Class<T> clazz) {List<Field> fields = new ArrayList<>();Class<?> superClazz = clazz;while (null != superClazz) {fields.addAll(Arrays.asList(superClazz.getDeclaredFields()));superClazz = superClazz.getSuperclass();}return fields;}/*** 将Map参数转换为字符串** @param map* @return*/public static String mapToString(Map<String, Object> map) {StringBuffer sb = new StringBuffer();map.forEach((key, value) -> {sb.append(key).append("=").append(value.toString()).append("&");});String str = sb.toString();str = str.substring(0, str.length() - 1);return str;}/*** 将Bean对象转换Url请求的字符串** @param t* @param <T>* @return*/public static <T> String getUrlByBean(T t) {String pre = "?";Map<String, Object> map = entityToMap(t);return pre + mapToString(map);}}

4.2 模拟前端获取Code

定义请求实体类 Oauth2AuthorizeRep:

@Data
public class Oauth2AuthorizeRep {/*** 公众号的唯一标识*/private String appid;/*** 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理*/private String redirect_uri;/*** 返回类型,请填写code*/private String response_type = "code";/*** 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)* snsapi_userinfo (弹出授权页面,可通过 openid 拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )*/private String scope;/*** 重定向后会带上 state 参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节*/private String state;
}

定义代码:

@Slf4j
@Controller
public class AuthController {/*** 模拟前端发起微信网页授权,再用户点击"微信登录"时,进行调用** @return* @throws UnsupportedEncodingException*/@GetMapping(value = "/pullCode")@ApiOperation(value = "用户请求进行授权及获取信息", notes = "用户请求进行授权及获取信息")public String code() throws UnsupportedEncodingException {log.info("------ 用户请求进行授权及获取信息 ------");// 设置回调地址 http://qh3wg7.natappfree.cc/wechat/getCodeString redirectUri = "http://qh3wg7.natappfree.cc/wechat/getCode";// urlEncode处理redirectUri = URLEncoder.encode(redirectUri, "utf-8");String url = "https://open.weixin.qq.com/connect/oauth2/authorize";// 封装url请求参数Oauth2AuthorizeRep rep = new Oauth2AuthorizeRep();rep.setAppid("wx79ec4331f29311b9");rep.setRedirect_uri(redirectUri);rep.setScope("snsapi_userinfo");rep.setState("STATE");// 参数的顺序必须是:appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirecturl = url + MapUtils.getUrlByBean(rep) + "#wechat_redirect";// 重定向url,微信会自动访问redirectUri,进行回调return "redirect:" + url;}/*** 模拟前端接收微信网页授权后回调,并且获取code;* 获取code后,由前端将code传入到后端的wxLogin接口中** @param code*/@GetMapping(value = "/getCode")@ApiOperation(value = "前端根据code获取信息", notes = "前端根据code获取信息")@ResponseBodypublic void auth(@RequestParam(value = "code") String code) {log.info("------ 回显Code:{} ------", code);}
}

4.3 授权绑定操作接口

定义获取用户信息请求实体类 Oauth2UserInfoRep:

@Data
public class Oauth2UserInfoRep {/*** 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同*/private String access_token;/*** 用户的唯一标识*/private String openid;/*** 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语*/private String lang = "zh_CN";}

定义获取用户信息响应实体类 Oauth2UserInfoRes:

@Data
public class Oauth2UserInfoRes {/*** 用户昵称*/private String nickname;/*** 用户的唯一标识*/private String openid;/*** 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知*/private Integer sex;/*** 用户个人资料填写的省份*/private String province;/*** 普通用户个人资料填写的城市*/private String city;/*** 国家,如中国为CN*/private String country;/*** 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),* 用户没有头像时该项为空。若用户更换头像,原有头像 URL 将失效。*/private String headimgurl;/*** 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)*/private List<String> privilege;/*** 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。*/private String unionid;
}

定义用户基本信息表实现类:

@Table(name = "tb_user_info")
@Data
public class UserInfo implements Serializable {/*** 主键*/@Idprivate Long id;/*** 登录手机号*/private String mobile;/*** 微信公众号号,唯一ID*/@Column(name = "openId")private String openId;/*** 性别*/private Integer gender;/*** 昵称*/@Column(name = "nickName")private String nickName;/*** 头像*/@Column(name = "headImage")private String headImage;
}

Controller实现:

    /*** 用户请求微信登录(由前端获取到)** @return* @throws UnsupportedEncodingException*/@GetMapping(value = "/wxLogin")@ApiOperation(value = "用户请求微信登录", notes = "用户请求微信登录")@ResponseBodypublic PageResult wxLogin(@RequestParam(value = "code") String code) {return authService.wxLogin(code);}/*** 绑定微信openId** @param mobile* @param smsCode* @param redisKey* @return*/@GetMapping(value = "/wxBind")@ApiOperation(value = "绑定微信openId", notes = "绑定微信openId")@ResponseBodypublic PageResult wxBind(@RequestParam("mobile") String mobile, @RequestParam("smsCode") String smsCode, @RequestParam("redisKey") String redisKey) {log.info("------ 绑定微信openId ------");//通过code获取用户及天气实时位置等信息return authService.wxBind(mobile, smsCode, redisKey);}

Service实现:


@Slf4j
@Service
public class AuthService {@Resourceprivate RestHttpRequest restHttpRequest;@Resourceprivate UserInfoMapper userInfoMapper;@Resourceprivate WxBean wxBean;public PageResult wxLogin(@RequestParam(value = "code") String code) {String appId = wxBean.getAppid();String secret = wxBean.getSecret();String url = wxBean.getApiUrl() + InterfaceConstant.OAUTH2_ACCESS_TOKEN;// 封装url请求参数Oauth2AccessTokenRep rep = new Oauth2AccessTokenRep();rep.setAppid(appId);rep.setSecret(secret);// 注意一个code只能使用一次,使用后需要重新模拟前端获取rep.setCode(code);url = url + MapUtils.getUrlByBean(rep);Map map = restHttpRequest.doHttp(url, HttpMethod.GET, null);if (map == null || map.get("errcode") != null) {throw new RuntimeException("获取授权信息失败!");}Oauth2AccessTokenRes res = new Oauth2AccessTokenRes();MapUtils.mapToEntity(map, res);log.info("Oauth2AccessTokenRes:" + JSON.toJSONString(res));// 获取accessToken及openId过期时间String accessToken = res.getAccess_token();String openid = res.getOpenid();// 判断openId是否存在绑定关系UserInfo userInfo = userInfoMapper.selectByOpenId(openid);if (userInfo != null) {// 存在绑定关系,表示可以直接登录,返回用户信息及登录验证的token信息return ResultUtils.success(userInfo);}// 判断当前用户是否已经登录了,模拟登录,当System.currentTimeMillis()偶数表示已经登录if (System.currentTimeMillis() % 2 == 0) {// 如果已登录则直接绑定openId并且返回绑定成功标识userInfo = new UserInfo();// TODO 如果已经登录了,业务系统直接根据token获取用户Id值userInfo.setId(1L);userInfo.setOpenId(openid);userInfoMapper.updateByPrimaryKeySelective(userInfo);return ResultUtils.success(null);} else {// 如果没有登录,将通过openId获取的微信信息存入redis中,将redisKey返回到前端String redisKey = UUIDUtils.getUuId();Oauth2UserInfoRes oauth2UserInfoRes = getAndInsertUserInfo(openid, accessToken);RedisUtils.setEx(redisKey, JSON.toJSONString(oauth2UserInfoRes), 60L, TimeUnit.MINUTES);return ResultUtils.success(redisKey);}}/*** 绑定微信openId** @param mobile* @param smsCode* @param redisKey* @return*/public PageResult wxBind(@RequestParam("mobile") String mobile, @RequestParam("smsCode") String smsCode, @RequestParam("redisKey") String redisKey) {if (!RedisUtils.hasKey(redisKey)) {return ResultUtils.fail("长时间未操作,请重新授权登录!");}Object o = RedisUtils.get(redisKey);Oauth2UserInfoRes oauth2UserInfoRes = JSON.parseObject(o.toString(), Oauth2UserInfoRes.class);// 验证手机号及验证码是否正确,如果System.currentTimeMillis()为偶数则表示不正确if (System.currentTimeMillis() % 2 == 0) {return ResultUtils.fail("验证码错误!");}// 删除keyRedisUtils.del(redisKey);// 通过手机号查询UserInfo userInfo = userInfoMapper.selectByMobile(mobile);// 如果手机号不存在于系统中,则将手机号、openId、redisKey中的基本信息插入到数据库中,实现用户的注册已经openId绑定;if (userInfo == null) {userInfo = new UserInfo();userInfo.setId(System.currentTimeMillis());userInfo.setGender(oauth2UserInfoRes.getSex());userInfo.setMobile(mobile);userInfo.setNickName(oauth2UserInfoRes.getNickname());userInfo.setOpenId(oauth2UserInfoRes.getOpenid());userInfo.setHeadImage(oauth2UserInfoRes.getHeadimgurl());userInfoMapper.insert(userInfo);// 返回成功return ResultUtils.success(userInfo);}if (userInfo.getOpenId() != null) {return ResultUtils.fail("手机号已绑定其他微信号,无法再次绑定!");} else {// 进行绑定操作userInfo.setOpenId(oauth2UserInfoRes.getOpenid());userInfoMapper.updateByPrimaryKeySelective(userInfo);return ResultUtils.success(userInfo);}}private Oauth2UserInfoRes getAndInsertUserInfo(String openid, String accessToken) {// 获取用户信息String url = wxBean.getApiUrl() + InterfaceConstant.OAUTH2_USERINFO;Oauth2UserInfoRep rep = new Oauth2UserInfoRep();rep.setAccess_token(accessToken);rep.setOpenid(openid);url = url + MapUtils.getUrlByBean(rep);Map userMap = restHttpRequest.doHttp(url, HttpMethod.GET, null);Oauth2UserInfoRes res = new Oauth2UserInfoRes();MapUtils.mapToEntity(userMap, res);// 打印信息log.info("UserInfo:" + JSON.toJSONString(res));return res;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/40321.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信授权登录流程以及公众号配置方法(golang后端)

一、准备一个已经认证OK的微信公众号和已经备案的域名&#xff0c;且解析好配置好https证书。 1.如上图 微信公众号 > 基本配置 &#xff0c;设置开发者密码 2.设置IP白名单&#xff0c;白名单填写提供后端服务的服务器公网IP 二、公众号服务器配置。 1.找到基本配置 2.将服…

微信公众号用户与网站用户的绑定方案

现在很多网站都已经建立了一套完整的用户账号体系&#xff0c;基于这套体系&#xff0c;再做其他应用的用户扩展就非常方便。例如&#xff0c;有了微软的outlook账户&#xff0c;就可以登录win8&#xff0c;可以登录微软的邮箱&#xff0c;还可以登录skype。同样地&#xff0c;…

微信开放平台 帐号管理 绑定在同一个开放平台帐号下的公众号及小程序让用户unionid一致...

开发十年&#xff0c;就只剩下这套架构体系了&#xff01; >>> 第三方平台在获得此权限后&#xff0c;可以代替已授权的公众号/小程序创建开放平台帐号或进行绑定/解绑操作。 绑定在同一个开放平台帐号下的公众号及小程序&#xff0c;用户unionid一致。开发者可调用…

wechat-0051,微信公众号,第三方登录—扫码绑定

声明&#xff1a;这边所谓的第三方登录&#xff0c;不是通过网页授权的方式&#xff0c;而是利用生成带参数的二维码&#xff0c;将微信号和平台账号绑定实现。如果你要做的是授权登录&#xff0c;请查看 https://blog.csdn.net/wrongyao/article/details/80229986 微信第三方…

IT创业项目 - 跟淘宝商城合作网赚项目,赚多少你说了算!

【项目介绍】&#xff1a; 使用淘宝商城的巨大流量销售产品或者服务。 【市场前景】&#xff1a; 淘宝网作为最大的B2C交易平台,淘宝商城又是淘宝网要点的发展对象&#xff0c;越来越多的年轻人喜爱在淘宝购买产品和服务。使用好淘宝的流量&#xff0c;赚钱十分简略。其实这是一…

微信公众号开发消息推送以及图文推送

今天给大家分享的关注公众号自动推送图文消息&#xff0c;以及做一个超牛逼的机器人。 先看看效果。 发错图了。。。这是我昨天开发的一款机器人chu了会骂人啥都不会了。我今天将它词库进行了更新和升级&#xff0c;接入了http://www.itpk.cn/ 机器人第三词库 先给你截图&…

【全网最简单】给朋友- 制作,微信公众号推送教程

简介&#xff1a; 前段时间&#xff0c;抖音非常火的微信公众号推送天气&#xff0c;生日&#xff0c;祝福等信息给女朋友专属推送&#xff0c;而且大部分都是Python写的&#xff0c;对于我来说&#xff0c;必须得整起&#xff0c;上java版本&#xff0c;到时候打个包&#xff…

小扎All in AIGC,连夜成立顶级产品团队

金磊 发自 凹非寺量子位 | 公众号 QbitAI ChatGPT的火爆&#xff0c;终究是让Meta坐不住了。 这不&#xff0c;小扎&#xff08;扎克伯格&#xff09;连夜对外宣布了公司的大动作&#xff1a; 成立顶级产品团队&#xff0c;专注AIGC。 这个团队可以说是整合了全公司搞AIGC的人才…

MySQL 被 PG 干翻!最赚钱的开发语言是他?

出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013) Stack Overflow 发布了 2023 年开发者调查报告&#xff0c;据称共计超过 9 万名开发者参与了此次调查。 完整报告包含了受访开发者画像&#xff0c;以及关于开发技术、AI、职业、社区等方面的内容。本文主要介绍关于…

文心一言员工跳槽工资翻倍,猎头:百万年薪很正常

整理 | 朱珂欣 出品 | CSDN程序人生&#xff08;ID&#xff1a;coder_life&#xff09; 前段时间&#xff0c;国内外科技圈呈现出群雄逐“ChatGPT”的态势&#xff1a;谷歌官宣竞品 Bard 、微软发布 AI 驱动的新版 Bing 搜索引擎和 Edge 浏览器、百度官宣“文心一言”、网易…

AIGC分享交流平台、GPT-4、GPT实时联网、Claude

拥有无限畅谈的AI个人助理&#xff0c;提高效率和创造力&#xff0c;引领未来的智能生活&#xff1b; 不仅承载着最前沿的科技理念&#xff0c;更集成了对人工智能可能性的深度理解。 已支持基于GPT、Claude等主流大模型的对话内容生成、支持GPT联网查询实时信息&#xff1b;基…

2022年休闲游戏市场总结

在预测 2023 年之前&#xff0c;我们先回顾一下 2022 年。从上一年发生的事件中往往能看到未来趋势的影子&#xff0c;所以 2022 年的总结至关重要。 一、2022年总结回顾 1、流行游戏类型 回顾 2022 年&#xff0c;三种超休闲游戏表现最为突出&#xff1a; 跑酷游戏&#xff1a…

破解VProtect所有版本,PATCH HWID方式过注册

运行后&#xff1a; 本机机器码"195F9059606EEB4723128A216ED1426B”&#xff0c;在内存中搜索该字符串&#xff0c;可以得到3个地方&#xff0c;我这里以最后搜索到的地方为切入点&#xff0c; 为什么&#xff1f; 找到的3个地方都下内存断点判断&#xff0c;刚好最后找到…

新加坡国立大学学霸,《快乐机器学习》和《Python 从入门到入迷》作者,FRM,CAIA...

王的机器主理人 王圣元 (FRM, CAIA) 某加密货币公司 Head of Quant 冬海集团 SeaMoney 建模负责人 八方咨询 量化总监 新加坡国立大学金融数学硕士 新加坡国立大学量化金融学士 《快乐机器学习》的作者 《Python 从入门到入迷》的作者 第一本书 《快乐机器学习》 第二本书《Pyt…

瑞芯微推出RV1126性能/案例大评测

瑞芯微推出RV1126性能/案例大评测 目录 案例1&#xff1a;面部识别 案例2&#xff1a;图像分割 案例3&#xff1a;目标检测 一、车载录像性能提升一倍,支持8路1080p视频录像 二、内置2T独立NPU,AI效率更高 三、配备安防级ISP,确保夜拍高清晰度 四、存储空间扩容一倍,记录…

【SAT】

A Tutorial to SAT Solving 约束求解基础与应用4.10 1. SAT的概念 Propositional Satisfiability (SAT)&#xff1a;Given a propositional formula φ, test whether there is an assignment to the variables that makes φ true. 公式组成&#xff1a; 布尔变量x litera…

2023年SAT、ACT、AP、Alevel、IB考试时间表

2023年已经来临&#xff01;以下是2023年SAT、ACT、AP、A-Level、IB考试时间安排&#xff0c;早规划早备考&#xff0c;建议大家收藏&#xff01;2023 SAT从2023年开始&#xff0c;美国以外的所有SAT考试都将转为机考&#xff0c;美国SAT考试将在2024年全面转为机考。2023年一共…

13万亿邮储银行数字化转型之路

中国邮政储蓄可追溯至1919 年开办的邮政储金业务&#xff0c;至今已有百年历史。2007年 3月&#xff0c;在改革原邮政储蓄管理体制基础上&#xff0c;中国邮政储蓄银行有限责任公司挂牌成立。2012年1月&#xff0c;本行整体改制为股份有限公司。2016年9月本行在香港联交所挂牌上…

国内的Ubuntu镜像源|Ubuntu清华镜像源

国内的Ubuntu镜像源|Ubuntu清华镜像源 今天学习docker需要在线Ubuntu镜像&#xff0c;所以做了一个镜像下载地址笔记&#xff0c;方面以后的下载。 官方镜像下载访问地址&#xff1a; https://cn.ubuntu.com/download/alternative-downloads 网页拉到最下&#xff0c;找到chi…

AI生成答辩PPT教程

一&#xff1a;通过”AI帮个忙“网站的PPT大纲生成器生成大纲 1 AI帮个忙 | 多功能AI小帮手点击网站进入 1 输入主题&#xff08;论文名&#xff09;会生成大纲 2 复制全部内容 二&#xff1a;通过大纲在AI生成PPT网站进行生成内容 1.通过网站生成&#xff0c;下面提供两种…