day18_支付宝支付项目部署(保存支付信息,支付接口,支付宝异步回调)

文章目录

  • 1 支付
    • 1.1 需求说明
    • 1.2 支付宝支付
      • 1.2.1 产品介绍
        • 产品特色
        • 使用示例
        • 申请条件
        • 费率
      • 1.2.2 接入准备
      • 1.2.3 手机网站支付快速接入
      • 1.2.4 官方demo研究
    • 1.3 环境搭建(service-pay)
    • 1.4 后端接口
      • 1.4.1 保存支付信息
        • 实现流程说明
        • 查询订单接口开发
        • openFeign接口定义
        • 代码实现
          • 添加依赖
          • 修改启动类
          • PaymentInfo
          • PaymentInfoService
          • PaymentInfoMapper
          • PaymentInfoMapper.xml
      • 1.4.2 支付接口
        • pom.xml
        • application-alipay.yml
        • AlipayProperties
        • 修改启动类(PayApplication)
        • AlipayConfiguration
        • AlipayController
        • AlipayService
        • 服务网关
      • 1.4.3 支付宝异步回调
        • 异步通知说明
        • 通知流程说明
        • 内网穿透介绍
        • 验证签名
        • 更新支付信息
          • PaymentInfoService
          • PaymentInfoMapper
          • PaymentInfoMapper.xml
        • 更新订单支付状态
        • 更新商品销量
          • SkuSaleDto
          • ProductController
          • ProductService
          • ProductSkuMapper
          • ProductSkuMapper.xml
          • ProductFeignClient
          • ProductFeignClientFallback
          • PaymentInfoService
  • 2 项目部署
    • 2.1 将配置文件导入Nacos
      • 2.1.1 引入依赖
      • 2.1.2 添加nacos配置文件
      • 2.1.3 修改application.yml
      • 2.1.4 启动项目测试
    • 2.2 docker部署
      • 2.2.0 Harbor准备
        • 2.2.0.1 修改harbor配置
        • 2.2.0.2 修改harbor-nginx容器端口号
        • 2.2.0.3 修改docker-registry通信安全地址
        • 2.2.0.4 docker服务端开启远程访问
        • 2.2.0.5 启动harbor所有容器
        • 2.2.0.6 在harbor控制台中创建spzx项目目录
      • 2.2.1 修改maven配置-settings.xml
      • 2.2.2 引入依赖插件
      • 2.2.4 编写dockerfile文件
      • 2.2.5 执行maven的打包命令
      • 2.2.6 拉取镜像部署
        • 部署
        • 启动

1 支付

1.1 需求说明

订单支付如图所示:

在这里插入图片描述

在支付页面点击确认支付按钮此时就需要对接第三方支付系统,给用户展示出第三方支付系统的收银台。

查看接口文档:

支付接口地址及返回结果:

get /api/order/alipay/submitAlipay/{orderNo}
返回结果:
支付宝支付H5表单

1.2 支付宝支付

官网地址:https://open.alipay.com/

支付宝(中国)网络技术有限公司 [1] 是国内的第三方支付平台,致力于提供“简单、安全、快速”的支付解决方案 [2] 。支付宝公司从2004年建立开始,始终以“信任”作为产品和服务的核心。旗下有“支付宝”与“支付宝钱包”两个独立品牌。自2014年第二季度开始成为当前全球最大的移动支付厂商。

1.2.1 产品介绍

产品特色

选择手机网站支付:https://open.alipay.com/api/detail?code=I1080300001000041949

在这里插入图片描述

手机网站支付是指商家在移动端网页展示商品或服务,用户在商家页面确认使用支付宝支付后,浏览器自动跳转支付宝 App 或支付宝网页完成付款的支付产品。该产品在签约完成后,需要技术集成方可使用。

使用示例

image

申请条件

支持的账号类型:支付宝企业账号、支付宝个人账号。

签约申请提交材料要求:

  • 提供网站地址,网站能正常访问且页面显示完整,网站需要明确经营内容且有完整的商品信息。
  • 网站必须通过 ICP 备案,且备案主体需与支付宝账号主体一致。若网站备案主体与当前账号主体不同时需上传授权函。
  • 个人账号申请,需提供营业执照,且支付宝账号名称需与营业执照主体一致。

注意:需按照要求提交材料,若部分材料不合格,收款额度将受到限制(单笔收款 ≤ 2000 元,单日收款 ≤ 20000 元)。若签约时未能提供相关材料(如营业执照),请在合约生效后的 30 天内补全,否则会影响正常收款。

费率
收费模式费率
单笔收费0.6%-1.0%

特殊行业费率为 1.0%,非特殊行业费率为 0.6%。特殊行业包含:休闲游戏、网络游戏点卡、游戏渠道代理、游戏系统商、网游周边服务、交易平台、网游运营商(含网页游戏)等。

1.2.2 接入准备

官方文档:https://opendocs.alipay.com/open/203/107084?pathHash=a33de091

整体流程:

在这里插入图片描述

为了提供数据传输的安全性,在进行传输的时候需要对数据进行加密:

常见的加密方式:

1、不可逆加密:只能会数据进行加密不能解密

2、可逆加密:可以对数据加密也可以解密

可逆加密可以再细分为:

1、对称加密: 加密和解密使用同一个秘钥

在这里插入图片描述

2、非对称加密:加密和解密使用的是不同的秘钥

在这里插入图片描述

支付宝为了提供数据传输的安全性使用了两个秘钥对:

在这里插入图片描述

沙箱环境使用步骤:

1、注册登录支付宝开放平台

2、进入支付宝开放平台控制台,选择沙箱环境

在这里插入图片描述

在这里插入图片描述

3、沙箱环境提供的应用已经绑定了相关产品

4、配置应用公钥:

在这里插入图片描述

需要下载支付宝秘钥生成器【https://opendocs.alipay.com/common/02khjo】,然后生成秘钥。

注意:使用沙箱买家账号进行测试,提前充值。

1.2.3 手机网站支付快速接入

官方文档:https://opendocs.alipay.com/open/203/105285?pathHash=ada1de5b

系统交互流程图:

在这里插入图片描述

作为我们的项目来讲只需要将支付宝的收银台展示给用户即可,后续支付的动作和我们的系统就没有关系了。支付成功以后,支付宝开放平台会请求我

们系统的接口通知支付结果,我们的系统也可以调用支付宝交易查询接口获取支付结果。

展示收银台流程图如下所示:

在这里插入图片描述

1.2.4 官方demo研究

步骤:

1、官方demo下载地址:https://opendocs.alipay.com/open/203/105910?pathHash=1a2e3a94

2、将访问demo的eclipse项目更改为idea的maven项目(jdk8)

3、在AlipayConfig类中填写参数信息

4、启动项目进行测试

1.3 环境搭建(service-pay)

步骤:

1、在spzx-service模块下创建一个service-pay微服务,并加入如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、准备application.yml、application-dev.yml、logback-spring.xml、mybatis-config.xml文件。文件内容如下所示:

server:port: 8514spring:application:name: service-paycloud:nacos:discovery:server-addr: 192.168.136.142:8848sentinel:transport:dashboard: localhost:8080datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.136.142:3306/db_spzx?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=trueusername: rootpassword: 1234data:redis:host: 192.168.136.142port: 6379password: 1234mybatis:config-location: classpath:mybatis-config.xmlmapper-locations: classpath:mapper/*/*.xml
feign:sentinel:enabled: true

logback-spring.xml修改输出路径:

<property name="log.path" value="D://work//service-pay//logs" />

mybatis-config.xml:从之前的模块中进行复制

3、创建启动类

// com.atguigu.spzx.pay;
@SpringBootApplication
@EnableUserWebMvcConfiguration
public class PayApplication {public static void main(String[] args) {SpringApplication.run(PayApplication.class , args) ;}}

1.4 后端接口

1.4.1 保存支付信息

实现流程说明

在支付之前需要保存支付信息到payment_info表中:

在这里插入图片描述

查询订单接口开发

在service-order微服务中需要提供一个根据orderNo查询订单信息的接口,步骤如下:

1、OrderMapper添加接口方法

OrderInfo getByOrderNo(String orderNo) ;

2、OrderMapper.xml映射文件添加如下sql语句

<select id="getByOrderNo" resultMap="orderInfoMap">select <include refid="columns" />from order_infowhereorder_no = #{orderNo}
</select>

3、OrderService业务层代码实现

// 业务接口
OrderInfo getByOrderNo(String orderNo) ;// 业务接口实现类
@Override
public OrderInfo getByOrderNo(String orderNo) {return orderInfoMapper.getByOrderNo(orderNo) ;
}

4、OrderController表现层代码实现

@Operation(summary = "获取订单信息")
@GetMapping("auth/getOrderInfoByOrderNo/{orderNo}")
public Result<OrderInfo> getOrderInfoByOrderNo(@Parameter(name = "orderId", description = "订单id", required = true) @PathVariable String orderNo) {OrderInfo orderInfo = orderInfoService.getByOrderNo(orderNo) ;return Result.build(orderInfo, ResultCodeEnum.SUCCESS);
}
openFeign接口定义

步骤:

1、在spzx-service-client模块下创建一个service-order-client的子模块

2、在service-order-client模块下定义远程openFeign接口

// com.atguigu.spzx.feign.order;
@FeignClient(value = "service-order" , fallback = OrderFeignClientFallback.class)
public interface OrderFeignClient {@GetMapping("/api/order/orderInfo/auth/getOrderInfoByOrderNo/{orderNo}")public Result<OrderInfo> getOrderInfoByOrderNo(@PathVariable String orderNo) ;}// com.atguigu.spzx.feign.order.fallback;
@Slf4j
public class OrderFeignClientFallback implements OrderFeignClient {@Overridepublic Result<OrderInfo> getOrderInfoByOrderNo(String orderNo) {log.info("OrderFeignClientFallback...getOrderInfoByOrderNo方法执行了");return Result.build(null , ResultCodeEnum.SUCCESS) ;}}

3、降级类自动化配置

在resources目录下创建一个MATE-INF/spring文件夹,在该文件夹下创建一个

org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,文件的中的内容如下所示:

com.atguigu.spzx.feign.order.fallback.OrderFeignClientFallback
代码实现
添加依赖

在service-pay微服务的pom.xml中添加service-order-client的依赖:

<dependency><groupId>com.atguigu.spzx</groupId><artifactId>service-order-client</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
修改启动类

在PayApplication启动类上添加如下代码:

@EnableFeignClients(basePackages = {"com.atguigu.spzx.feign.order"
})
PaymentInfo

针对当前要操作的数据定义一个与之对应的实体类:

// com.atguigu.spzx.model.entity.pay;
@Data
@Schema(description = "支付信息实体类")
public class PaymentInfo extends BaseEntity {private static final long serialVersionUID = 1L;@Schema(description = "用户id")private Long userId;@Schema(description = "订单号")private String orderNo;@Schema(description = "付款方式:1-微信 2-支付宝")private Integer payType;@Schema(description = "交易编号(微信或支付)")private String outTradeNo;@Schema(description = "支付金额")private BigDecimal amount;@Schema(description = "交易内容")private String content;@Schema(description = "支付状态:0-未支付 1-已支付")private Integer paymentStatus;@Schema(description = "回调时间")private Date callbackTime;@Schema(description = "回调信息")private String callbackContent;}
PaymentInfoService
//业务接口
public interface PaymentInfoService {PaymentInfo savePaymentInfo(String orderNo);
}//业务接口实现
// com.atguigu.spzx.pay.service.impl;
@Service
public class PaymentInfoServiceImpl implements PaymentInfoService {@Autowiredprivate PaymentInfoMapper paymentInfoMapper ;@Autowiredprivate OrderFeignClient orderFeignClient ;@Overridepublic PaymentInfo savePaymentInfo(String orderNo) {// 查询支付信息数据,如果已经已经存在了就不用进行保存(一个订单支付失败以后可以继续支付)PaymentInfo paymentInfo = paymentInfoMapper.getByOrderNo(orderNo);if(null == paymentInfo) {OrderInfo orderInfo = orderFeignClient.getOrderInfo(Long.parseLong(orderNo)).getData();paymentInfo = new PaymentInfo();paymentInfo.setUserId(orderInfo.getUserId());paymentInfo.setPayType(orderInfo.getPayType());String content = "";for(OrderItem item : orderInfo.getOrderItemList()) {content += item.getSkuName() + " ";}paymentInfo.setContent(content);paymentInfo.setAmount(orderInfo.getTotalAmount());paymentInfo.setOrderNo(orderNo);paymentInfo.setPaymentStatus(0);paymentInfoMapper.save(paymentInfo);}return paymentInfo;}
}
PaymentInfoMapper

持久层代码实现:

@Mapper
public interface PaymentInfoMapper {void save(PaymentInfo paymentInfo);PaymentInfo getByOrderNo(String orderNo);
}
PaymentInfoMapper.xml

在映射文件中定义对应的sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.atguigu.spzx.order.mapper.PaymentInfoMapper"><resultMap id="paymentInfoMap" type="com.atguigu.spzx.model.entity.order.PaymentInfo" autoMapping="true"></resultMap><!-- 用于select查询公用抽取的列 --><sql id="columns">id,user_id,order_no,pay_type,out_trade_no,amount,content,payment_status,callback_time,callback_content,create_time,update_time,is_deleted</sql><insert id="save" useGeneratedKeys="true" keyProperty="id">insert into payment_info (id,user_id,order_no,pay_type,out_trade_no,amount,content,payment_status,callback_time,callback_content) values (#{id},#{userId},#{orderNo},#{payType},#{outTradeNo},#{amount},#{content},#{paymentStatus},#{callbackTime},#{callbackContent})</insert><select id="getByOrderNo" resultMap="paymentInfoMap">select <include refid="columns" />from payment_infowhereorder_no = #{orderNo}</select></mapper>

1.4.2 支付接口

pom.xml

在service-pay微服务中添加alipay的依赖:

<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId>
</dependency>
application-alipay.yml

支付宝示例demo:https://opendocs.alipay.com/open/203/105285?pathHash=ada1de5b

将支付宝所需要的参数定义配置文件中,以提高代码的维护性:

spzx:alipay:alipay_url: https://openapi.alipaydev.com/gateway.doapp_id: 2021000122609658app_private_key: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXHXwKl51d5htttzJDQg1Oq+w/RAdjdGlTgGlodWxM5Vszd9IgnEffB2HlsGcpgHDteppONr8rWsEy/LmwaMR9C83YfgFPRbSIYATrQw1VynuwEFwvlW5FBT00QUqmL1AXaFGWLAao8xjRjQiArhnQA+o88DXEVnUwHTWFd8aOymesOUjJGWOId6x1MnK8om66Zxc/QFR/vZoQaE8YrATNGMMd1O1CGsnwgJ1bBOcG1Cv1dF903gllGwkLhSE3LK1/SbUg0fUi8mHU0EUyasbknlFqBdvFvJZZJ0NC+Z2sZqXV57DLa8M7bp6+YsDuc0o0EAVnLx7beYlDjwGDMgqxAgMBAAECggEAJo9UfpNviW1VJGrxvW3WXXPLRd2DESK8WZ1TyF7mMrz3x6tUiBO41zVYCrc3q8RljIOTak/X+iUfVXZdn6EsOkhPz2Vfyi2cQoxV1P54IaMYarXSACZeS+hpVLMwbDV4d3CcGPjE/kmB1L7rI4LJfWXyWHhnD+GL56ocZSFKHlcsY2bx99T+HHKTretBRnLQ8q8/iZLkTbxReaMd3o9dGTqS75d3O1nT4u0A8Pupo2dPrlE7NvtOLJMEKixToJPAfJ0b2/H1nxV19/ZW3xvRPJjSIdx32ULuUIyzkAMlH5jwO3D9NMR8fbLcsewgDAif0sPB3USpUT/4AfmJAdcVrQKBgQDf1DnUXQ/JPH/SS78W1EdUzvhGjead1NCG70gZH9YKWS3+l4wkl7l1bqrXGe17jVnPD0vHQZT7V9MjQpa0n9mGU6jKt7ym27BQwF6CLqLE82ITKKqRhUAY7D/TpXPD+DI4STmRqEWDzCgAeX2B9Y7MtOndlExPh8ZxPKtPxDPsNwKBgQCs1cH8h/nGXMv2I0hLPdIKVAQRPDCVBpzuycxn2ezHDcz5rBrYsjOpdNr3SWzcavduGI4A673uWa5znO2KE4e8Y8Uoi75wI7nx4/VapsnS8IuqpIOpkLR2ovEjxGz1BI6QyIg1Xl3QFF65BBVEucgYeLXvt/dMdUA7Z7id/h9cVwKBgDZkZmE69Dkc4JsEGT28/FCZsy/CEAbOzpXb1BN27xa4sTqrLT0/OaxV5mI7RMC/itGMkAet4jxqDT8GUYU3Sy8faWdJ2yhZPrGA7faIyrk9w9mQClMupHLqBmCyVj2LNPkEol7JG4t5s0baPyuztq38UNCt1xWEky61ZZQOw+dlAoGAQdEhD0bEwlpCPZhQBn8jRlWaOun94jJjfreQRJgDiAXkYcu9aXnrHIPogrUOZJ3DXcSyBv2/FU5HlbVT6/nl/cLMqNUWj2O7grb5jyzmvJJnzXLaxK7bWjZQt/ssNt4mYFJNNG2cMgofzDsW0lYhMdh+CCy5Wv9nl3e3IUtNq/8CgYASPcPdaCBLzCSGlTV9HMhQwRhOpWLOzQNKprebQf0fubNFGd6+yfM6DdejHXf6KH4IgV9l8OPe5ro85tmrBkvMlbh7KHbpYJ/V9cdMKd+kbxoJTkRKCnoZhY5QSuEMoC8OB1qhzJeuoqUvmpi0q569IBXrxZguD29ZqwGxoa1KNg==alipay_public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk41ooyX7utKM9B7jNcc4EzmyVU0Qfs18KFVFwyhl7YQMw/PB2YVQreVSvvb1rS/2YVxcjLK/9ehD//79b8aoxhFlEGqA7fGu0C2UR6pl+PhmRLcHeyN+DOG87Fhqb1t4JXmXQc1LXUfelJoh+r5XnMPWDAlY5JJtH3GZIU+AoBt9PuEtfhh03LL6WtnJMwOnqH94T8qHymLDftEGOWme1iAlenB692cUId20BmLJal621EAN+xpmkeJZEpx1wQ2fGhyTo7pm4v8LVLuqzOXkraffITvfbPl5IU0kjjs/QECwItAI0IBbNsDutezw/a0JobijjoF28uo4gtwmncBoQwIDAQABreturn_payment_url: http://192.168.136.142/#/pages/money/paySuccessnotify_payment_url: http://127.0.0.1:8500/api/order/alipay/callback/notify

在application-dev.yml文件中导入该配置:

spring:config:import: application-alipay.yml
AlipayProperties

定义一个实体类读取配置文件内

// com.atguigu.spzx.pay.properties;
@Data
@ConfigurationProperties(prefix = "spzx.alipay")
public class AlipayProperties {private String alipayUrl;private String appPrivateKey;public  String alipayPublicKey;private String appId;public  String returnPaymentUrl;public  String notifyPaymentUrl;public final static String format="json";public final static String charset="utf-8";public final static String sign_type="RSA2";}
修改启动类(PayApplication)

在启动类上添加**@EnableConfigurationProperties**注解,开启通过实体类读取配置文件内容封装数据功能:

@EnableConfigurationProperties(value = { AlipayProperties.class })
AlipayConfiguration

定义一个AlipayConfiguration的配置类,配置发送请求的核心对象:AlipayClient

//  com.atguigu.spzx.pay.configuration;
@Configuration
public class AlipayConfiguration {@Autowiredprivate AlipayProperties alipayProperties ;@Beanpublic AlipayClient alipayClient(){AlipayClient alipayClient = new DefaultAlipayClient(alipayProperties.getAlipayUrl() ,alipayProperties.getAppId() ,alipayProperties.getAppPrivateKey() ,AlipayProperties.format ,AlipayProperties.charset ,alipayProperties.getAlipayPublicKey() ,AlipayProperties.sign_type );return alipayClient;}}
AlipayController
// com.atguigu.spzx.pay.controller
@Controller
@RequestMapping("/api/order/alipay")
public class AlipayController {@Autowiredprivate AlipayService alipayService;@Operation(summary="支付宝下单")@GetMapping("submitAlipay/{orderNo}")@ResponseBodypublic Result<String> submitAlipay(@Parameter(name = "orderNo", description = "订单号", required = true) @PathVariable(value = "orderNo") String orderNo) {String form = alipayService.submitAlipay(orderNo);return Result.build(form, ResultCodeEnum.SUCCESS);}
}
AlipayService
//业务接口
public interface AlipayService {String submitAlipay(String orderNo);
}//业务接口实现
// com.atguigu.spzx.pay.service.impl;
@Slf4j
@Service
public class AlipayServiceImpl implements AlipayService {@Autowiredprivate AlipayClient alipayClient;@Autowiredprivate PaymentInfoService paymentInfoService;@Autowiredprivate AlipayProperties alipayProperties ;@SneakyThrows  // lombok的注解,对外声明异常@Overridepublic String submitAlipay(String orderNo) {//保存支付记录PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(orderNo);//创建API对应的requestAlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();// 同步回调alipayRequest.setReturnUrl(alipayProperties.getReturnPaymentUrl());// 异步回调alipayRequest.setNotifyUrl(alipayProperties.getNotifyPaymentUrl());// 准备请求参数 ,声明一个map 集合HashMap<String, Object> map = new HashMap<>();map.put("out_trade_no",paymentInfo.getOrderNo());map.put("product_code","QUICK_WAP_WAY");map.put("total_amount",paymentInfo.getAmount());map.put("subject",paymentInfo.getContent());alipayRequest.setBizContent(JSON.toJSONString(map));// 发送请求AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest);if(response.isSuccess()){log.info("调用成功");return response.getBody();} else {log.info("调用失败");throw new GuiguException(ResultCodeEnum.DATA_ERROR);}}}
服务网关

在spzx-server-gateway网关中配置service-pay微服务的路由规则:

spring:cloud:gateway:routes:- id: service-payuri: lb://service-paypredicates:- Path=/api/order/alipay/**

1.4.3 支付宝异步回调

异步通知说明

官网地址:https://opendocs.alipay.com/open/203/105286?pathHash=022a439c&ref=api

在这里插入图片描述

通知流程说明

当用户支付成功以后,支付宝系统会调用我们系统的接口通知支付结果,整体流程如下所示:

在这里插入图片描述

内网穿透介绍

在service-pay微服务中开发一个接口供支付宝进行调用:

// com.atguigu.spzx.pay.controller.AlipayController
@RequestMapping("callback/notify")
@ResponseBody
public String alipayNotify(@RequestParam Map<String, String> paramMap, HttpServletRequest request) {log.info("AlipayController...alipayNotify方法执行了...");return "success" ;
}

当支付成功以后支付宝无法调用本地接口,因为本地接口是位于一个私有IP地址范围内,并且被路由器或防火墙等设备保护起来。这个私有的网络设备无法直接从公共网络访问,该问题的解决可以使用内网穿透技术。

内网穿透:内网穿透(Intranet Port Forwarding)是一种将本地网络中的服务暴露给公共网络访问的技术。

内网穿透通过在公共网络上建立一个中转服务器,使得公共网络上的设备可以通过该中转服务器访问内网中的设备和服务。具体而言,内网穿透技术允

许您在公共网络上使用一个公网IP地址和端口号来映射到内网中的某个设备或服务的私有IP地址和端口号。

在这里插入图片描述

常见的内网穿透工具包括natapp、Ngrok、frp、花生壳等。

官网地址:https://natapp.cn/

试用步骤:

1、注册用户

2、购买隧道

在这里插入图片描述

3、购买二级域名,绑定隧道

在这里插入图片描述

4、下载客户端

在这里插入图片描述

5、客户端使用教程:https://natapp.cn/article/nohup

natapp.exe -authtoken=xxxxx

authtoken信息获取:

在这里插入图片描述

验证签名

支付宝回传过来的数据需要进行合法性的校验,校验通过以后才可以走后续的流程,具体代码如下所示:

// com.atguigu.spzx.pay.controller.AlipayController
@Operation(summary="支付宝异步回调")
@RequestMapping("callback/notify")
@ResponseBody
public String alipayNotify(@RequestParam Map<String, String> paramMap, HttpServletRequest request) {log.info("AlipayController...alipayNotify方法执行了...");boolean signVerified = false; //调用SDK验证签名try {signVerified = AlipaySignature.rsaCheckV1(paramMap, alipayProperties.getAlipayPublicKey(), AlipayProperties.charset, AlipayProperties.sign_type);} catch (AlipayApiException e) {e.printStackTrace();}// 交易状态String trade_status = paramMap.get("trade_status");// trueif (signVerified) {// TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failureif ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {// 正常的支付成功,我们应该更新交易记录状态paymentInfoService.updatePaymentStatus(paramMap, 2);return "success";}} else {// TODO 验签失败则记录异常日志,并在response中返回failure.return "failure";}return "failure";
}
更新支付信息
PaymentInfoService
//业务接口
void updatePaymentStatus(Map<String, String> map, Integer payType);@Transactional
@Override
public void updatePaymentStatus(Map<String, String> map, Integer payType) {// 查询PaymentInfoPaymentInfo paymentInfo = paymentInfoMapper.getByOrderNo(map.get("out_trade_no"));if (paymentInfo.getPaymentStatus() == 1) {return;}//更新支付信息paymentInfo.setPaymentStatus(1);paymentInfo.setOutTradeNo(map.get("trade_no"));paymentInfo.setCallbackTime(new Date());paymentInfo.setCallbackContent(JSON.toJSONString(map));paymentInfoMapper.updateById(paymentInfo);}
PaymentInfoMapper
void updateById(PaymentInfo paymentInfo);
PaymentInfoMapper.xml
<update id="updateById" >update payment_info set<if test="userId != null and userId != ''">user_id = #{userId},</if><if test="orderNo != null and orderNo != ''">order_no = #{orderNo},</if><if test="payType != null">pay_type = #{payType},</if><if test="outTradeNo != null and outTradeNo != ''">out_trade_no = #{outTradeNo},</if><if test="amount != null and amount != ''">amount = #{amount},</if><if test="content != null and content != ''">content = #{content},</if><if test="paymentStatus != null">payment_status = #{paymentStatus},</if><if test="callbackTime != null">callback_time = #{callbackTime},</if><if test="callbackContent != null and callbackContent != ''">callback_content = #{callbackContent},</if>update_time =  now()whereid = #{id}
</update>
更新订单支付状态

具体步骤:

1、在service-order微服务中开发一个更新订单支付状态的接口供service-pay微服务进行调用

// com.atguigu.spzx.order.controller.OrderInfoController
@Operation(summary = "获取订单分页列表")
@GetMapping("auth/updateOrderStatusPayed/{orderNo}/{orderStatus}")
public Result updateOrderStatus(@PathVariable(value = "orderNo") String orderNo , @PathVariable(value = "orderStatus") Integer orderStatus) {orderInfoService.updateOrderStatus(orderNo , orderStatus);return Result.build(null , ResultCodeEnum.SUCCESS) ;
}// com.atguigu.spzx.order.service.impl.OrderInfoServiceImpl
@Transactional
@Override
public void updateOrderStatus(String orderNo, Integer orderStatus) {// 更新订单状态OrderInfo orderInfo = orderInfoMapper.getByOrderNo(orderNo);orderInfo.setOrderStatus(1);orderInfo.setPayType(orderStatus);orderInfo.setPaymentTime(new Date());orderInfoMapper.updateById(orderInfo);// 记录日志OrderLog orderLog = new OrderLog();orderLog.setOrderId(orderInfo.getId());orderLog.setProcessStatus(1);orderLog.setNote("支付宝支付成功");orderLogMapper.save(orderLog);
}// com.atguigu.spzx.order.mapper.OrderInfoMapper
void updateById(OrderInfo orderInfo);

OrderInfoMapper.xml映射文件中添加sql语句:

<update id="updateById" >update order_info set<if test="userId != null and userId != ''">user_id = #{userId},</if><if test="nickName != null and nickName != ''">nick_name = #{nickName},</if><if test="orderNo != null and orderNo != ''">order_no = #{orderNo},</if><if test="couponId != null and couponId != ''">coupon_id = #{couponId},</if><if test="totalAmount != null and totalAmount != ''">total_amount = #{totalAmount},</if><if test="couponAmount != null and couponAmount != ''">coupon_amount = #{couponAmount},</if><if test="originalTotalAmount != null and originalTotalAmount != ''">original_total_amount = #{originalTotalAmount},</if><if test="feightFee != null and feightFee != ''">feight_fee = #{feightFee},</if><if test="payType != null">pay_type = #{payType},</if><if test="orderStatus != null">order_status = #{orderStatus},</if><if test="receiverName != null and receiverName != ''">receiver_name = #{receiverName},</if><if test="receiverPhone != null and receiverPhone != ''">receiver_phone = #{receiverPhone},</if><if test="receiverTagName != null and receiverTagName != ''">receiver_tag_name = #{receiverTagName},</if><if test="receiverProvince != null and receiverProvince != ''">receiver_province = #{receiverProvince},</if><if test="receiverCity != null and receiverCity != ''">receiver_city = #{receiverCity},</if><if test="receiverDistrict != null and receiverDistrict != ''">receiver_district = #{receiverDistrict},</if><if test="receiverAddress != null and receiverAddress != ''">receiver_address = #{receiverAddress},</if><if test="paymentTime != null">payment_time = #{paymentTime},</if><if test="deliveryTime != null">delivery_time = #{deliveryTime},</if><if test="receiveTime != null">receive_time = #{receiveTime},</if><if test="remark != null and remark != ''">remark = #{remark},</if><if test="cancelTime != null and cancelTime != ''">cancel_time = #{cancelTime},</if><if test="cancelReason != null and cancelReason != ''">cancel_reason = #{cancelReason},</if>update_time =  now()whereid = #{id}
</update>

2、openFeign远程调用接口定义

// com.atguigu.spzx.feign.order.OrderFeignClient
@FeignClient(value = "service-order" , fallback = OrderFeignClientFallback.class)
public interface OrderFeignClient {@GetMapping("/api/order/orderInfo/auth/updateOrderStatusPayed/{orderNo}/{orderStatus}")public abstract Result updateOrderStatus(@PathVariable(value = "orderNo") String orderNo , @PathVariable(value = "orderStatus") Integer orderStatus) ;
}

3、PaymentInfoService业务代码修改

// com.atguigu.spzx.pay.service.impl.PaymentInfoServiceImpl
@Transactional
@Override
public void updatePaymentStatus(Map<String, String> map, Integer payType) {// 1、查询PaymentInfo// 2、更新支付信息// 3、更新订单的支付状态orderFeignClient.updateOrderStatus(paymentInfo.getOrderNo() , payType) ;}
更新商品销量

操作模块:service-product

SkuSaleDto

定义远程调用传输的数据的实体类:

@Data
public class SkuSaleDto {private Long skuId;private Integer num;}
ProductController

表现层代码:

@Operation(summary = "更新商品sku销量")
@PostMapping("updateSkuSaleNum")
public Boolean updateSkuSaleNum(@RequestBody List<SkuSaleDto> skuSaleDtoList) {return productService.updateSkuSaleNum(skuSaleDtoList);
}
ProductService

业务层代码实现:

//业务接口
Boolean updateSkuSaleNum(List<SkuSaleDto> skuSaleDtoList);//业务接口实现
@Transactional
@Override
public Boolean updateSkuSaleNum(List<SkuSaleDto> skuSaleDtoList) {if(!CollectionUtils.isEmpty(skuSaleDtoList)) {for(SkuSaleDto skuSaleDto : skuSaleDtoList) {productSkuMapper.updateSale(skuSaleDto.getSkuId(), skuSaleDto.getNum());}}return true;
}
ProductSkuMapper
void updateSale(@Param("skuId") Long skuId, @Param("num") Integer num);
ProductSkuMapper.xml
<update id="updateSale" >update product_sku set sale_num = sale_num + #{num}, stock_num = stock_num - #{num}, update_time =  now() where id = #{skuId}
</update>
ProductFeignClient

操作模块:service-product-client

远程调用Feign接口

/*** 更新商品sku销量* @param skuSaleDtoList* @return*/
@PostMapping("/api/product/updateSkuSaleNum")
Boolean updateSkuSaleNum(@RequestBody List<SkuSaleDto> skuSaleDtoList);
ProductFeignClientFallback

操作模块:service-product-client

远程调用服务降级容错类

@Override
public Boolean updateSkuSaleNum(List<SkuSaleDto> skuSaleDtoList) {log.info("ProductFeignClientFallback...updateSkuSaleNum的方法执行了");return false ;
}
PaymentInfoService

修改PaymentInfoService业务代码修改

@Transactional
@Override
public void updatePaymentStatus(Map<String, String> map, Integer payType) {// 1、查询PaymentInfo// 2、更新支付信息// 3、更新订单的支付状态// 4、更新商品销量OrderInfo orderInfo = orderFeignClient.getOrderInfoByOrderNo(paymentInfo.getOrderNo()).getData();List<SkuSaleDto> skuSaleDtoList = orderInfo.getOrderItemList().stream().map(item -> {SkuSaleDto skuSaleDto = new SkuSaleDto();skuSaleDto.setSkuId(item.getSkuId());skuSaleDto.setNum(item.getSkuNum());return skuSaleDto;}).collect(Collectors.toList());productFeignClient.updateSkuSaleNum(skuSaleDtoList) ;}

2 项目部署

2.1 将配置文件导入Nacos

以service-user为例,其他模块类似

2.1.1 引入依赖

spzx-service模块引入依赖

<!-- 服务配置 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2.1.2 添加nacos配置文件

在nacos服务器添加配置文件

在这里插入图片描述

将application-dev.yml文件中的内容复制到nacos的service-user-dev.yml配置中:

server:port: 8512spring:application:name: service-usercloud:nacos:discovery:server-addr: 192.168.136.142:8848sentinel:transport:dashboard: localhost:8080datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.136.142:3306/db_spzx?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=trueusername: rootpassword: 1234data:redis:host: 192.168.136.142port: 6379password: 1234mybatis:config-location: classpath:mybatis-config.xmlmapper-locations: classpath:mapper/*/*.xml

2.1.3 修改application.yml

修改application.yml文件的内容如下所示:

spring:cloud:nacos:config:server-addr: 192.168.1.170:8848config:import:- nacos:service-user-dev.yml

2.1.4 启动项目测试

正常运行,接口正常访问

说明:

1、service-cart、service-pay、service-product、service-order按以上步骤调整(不需再次引入依赖)

2、spzx-server-gateway服务网关也是类似,注意单独引入依赖

2.2 docker部署

基于第一天docker环境及Harbor部署

2.2.0 Harbor准备

因为服务端80端口号被占用,所以修改harbor端口号为82

2.2.0.1 修改harbor配置
# 编辑配置 修改端口号为82
vim /opt/harbor/harbor.yml

在这里插入图片描述

2.2.0.2 修改harbor-nginx容器端口号
vim /opt/harbor/docker-compose.yml

在这里插入图片描述

2.2.0.3 修改docker-registry通信安全地址
vim /etc/docker/daemon.json

在这里插入图片描述

2.2.0.4 docker服务端开启远程访问
#修改该文件
vim /lib/systemd/system/docker.service#找到ExecStart行,修改成如下内容
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H fd:// --containerd=/run/containerd/containerd.socksystemctl daemon-reload				#重启守护进程
systemctl restart docker			#重启docker

注意:关闭防火墙[systemctl stop firewalld.service]

必须重启docker

2.2.0.5 启动harbor所有容器

执行harbor的安装启动脚本:

/opt/harbor/install.sh

浏览器访问harbor测试:

http://虚拟机ip:82
账号密码:admin/Harbor12345
2.2.0.6 在harbor控制台中创建spzx项目目录

在这里插入图片描述

2.2.1 修改maven配置-settings.xml

在maven的settings.xml文件中配置harbor服务的账号信息:

<server><id>harbor</id><username>admin</username><password>Harbor12345</password><configuration><email>123456@aliyun.com</email></configuration>
</server>

2.2.2 引入依赖插件

spzx-user为例,其他模块类似

pom.xml文件添加docker插件

<properties><!--        harbor地址 --><docker.repostory>192.168.1.170:82</docker.repostory><!--        连接harbor的项目名称:和上面创建的项目名称一样 --><docker.registry.name>spzx</docker.registry.name>
</properties><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>1.2.2</version><!--将插件绑定在某个phase执行--><executions><execution><id>build-image</id><!--将插件绑定在package这个phase(阶段)上。也就是说,用户只需执行mvn package,就会自动执行mvn docker:build--><phase>package</phase><goals><goal>build</goal><goal>push</goal></goals></execution></executions><configuration><serverId>harbor</serverId><registryUrl>http://${docker.repostory}</registryUrl><dockerHost>http://192.168.1.170:2375</dockerHost>  <!-- 配置docker主机地址 --><!--指定生成的镜像名--><imageName>${docker.repostory}/${docker.registry.name}/${project.artifactId}:${project.version}</imageName><!-- 指定 dockerfile 路径--><dockerDirectory>${project.basedir}</dockerDirectory><!-- 是否跳过docker构建 --><skipDockerBuild>false</skipDockerBuild></configuration></plugin></plugins>
</build>

插件原理:就是在docker主机上构建docker对应的镜像,然后将构建的镜像推送到harbor远程仓库中。

2.2.4 编写dockerfile文件

该文件的位置必须是和pom.xml处于同一个目录

FROM centos7-jdk17
MAINTAINER atguigu
EXPOSE 8512
ADD target/service-user-1.0-SNAPSHOT.jar /service-user-1.0-SNAPSHOT.jarWORKDIR /
ENTRYPOINT ["java" , "-jar" , "service-user-1.0-SNAPSHOT.jar"]

2.2.5 执行maven的打包命令

mvn clean package -DskipTests								# 打包跳过测试
mvn clean package -DskipTests -DskipdockerBuild				# 打包跳过测试的同时提高构建

控制台打印如图:

在这里插入图片描述

Harbor查看镜像:

在这里插入图片描述

注意:需要先在Harbor上将spzx项目创建出来

2.2.6 拉取镜像部署

部署

使用docker compose部署

service-user.yml文件的内容如下所示:

services:service-user:container_name: service-userimage: 192.168.1.170/spzx/service-user:1.0-SNAPSHOTports:- "8512:8512"

**注意:**拉取失败,请在docker中添加安全访问权限

# 编辑/etc/docker/daemon.json文件
vim /etc/docker/daemon.json# 添加安全访问权限
{"insecure-registries":["http://192.168.1.170:82"]
}# 重启Docker
systemctl restart docker

docker compose相关命令复习

# 启动容器(如果不存在容器就创建、存在则修改)
docker compose -f docker-compose.yml up -d# 删除所有容器
docker compose -f docker-compose.yml down# 停止所有容器
docker compose -f docker-compose.yml stop# 启动所有容器
docker compose -f docker-compose.yml start# 重启所有容器
docker compose -f docker-compose.yml restart
启动

docker compose相关命令:

# 启动容器(如果不存在容器就创建、存在则修改)
docker compose -f service-user.yml up -d

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

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

相关文章

Kafka MQ 主题和分区

Kafka MQ 主题和分区 Kafka 的消息通过 主题 进行分类。主题就好比数据库的表&#xff0c;或者文件系统里的文件夹。主题可以被分为若干个 分区 &#xff0c;一个分区就是一个提交日志。消息以追加的方式写入分区&#xff0c;然 后以先入先出的顺序读取。要注意&#xff0c;由…

新一代 Git 工具,AI 赋能!深度集成、简化操作 | 开源日报 No.194

gitbutlerapp/gitbutler Stars: 7.2k License: NOASSERTION gitbutler 是一个基于 Git 的版本控制客户端。旨在为现代工作流程构建一个全新的 Git 分支管理工具。 虚拟分支&#xff1a;可以同时在多个分支上工作&#xff0c;而无需不断切换分支简化提交管理&#xff1a;通过拖…

勒索软件事件手册:综合指南

近年来&#xff0c;勒索软件攻击的频率和复杂程度都急剧增加。这些攻击的影响可能是毁灭性的&#xff0c;从经济损失到严重的运营中断。 这就是为什么对于希望防范这种网络安全威胁的企业来说&#xff0c; 强大的勒索软件事件响应手册是不可谈判的。 本指南旨在深入了解勒索软…

项目管理技巧分享:如何有效拆分复杂项目?

项目经理需要理清三个问题&#xff1a;1.项目的达成目标是什么&#xff0c;2.项目执行周期有多长&#xff0c;3.项目预计成本是多少&#xff1f;复杂项目在管理执行上难度都较大&#xff0c;通常需要进行分解&#xff0c;并且这个过程离不开项目管理系统的支持。这篇文章告诉你…

《解密云计算:企业之选》

前言 在当今数字化时代,企业面临着巨大的数据处理压力和信息化需求,传统的IT架构已经无法满足日益增长的业务需求。在这样的背景下,越来越多的企业开始转向云计算,以实现灵活、高效和可扩展的IT资源管理和利用。 云计算 云计算是一种基于互联网的计算模式,它通过将数据、…

【字符串】【分类讨论】【KMP】1163. 按字典序排在最后的子串

作者推荐 视频算法专题 本文涉及知识点 字符串 字典序 分类讨论 本题无法使用KMP&#xff0c;因为t1不段变化。 LeetCode1163. 按字典序排在最后的子串 给你一个字符串 s &#xff0c;找出它的所有子串并按字典序排列&#xff0c;返回排在最后的那个子串。 示例 1&#xf…

Windows 安装 Xinference

Windows 安装 Xinference 0. 引言1. 创建虚拟环境2. 安装 pytorch3. 安装 llama_cpp_python4. 安装 chatglm-cpp5. 安装 Xinference6. 设置 model 路径7. 启动 Xinference8. 查看 Cluster Information 0. 引言 Xorbits Inference&#xff08;Xinference&#xff09;是一个性能…

伊理威科技:新手开抖店的教程

在数字浪潮中&#xff0c;抖音小店如星火燎原&#xff0c;吸引无数创业者。你是否也心潮澎湃&#xff0c;想要一试身手?别急&#xff0c;让我们一步步揭开开店的神秘面纱。 注册流程。想象一下&#xff0c;你只需在抖音平台上点击“我要开店”&#xff0c;按提示填写相关信息&…

物联网在智慧城市建设中的关键作用:连接、感知、智能响应

一、引言 随着信息技术的飞速发展&#xff0c;物联网&#xff08;IoT&#xff09;技术已经渗透到我们生活的方方面面&#xff0c;特别是在智慧城市建设中发挥着至关重要的作用。智慧城市是指通过运用先进的信息和通信技术&#xff0c;实现城市基础设施、公共服务、交通管理、环…

opencv dnn模块 示例(24) 目标检测 object_detection 之 yolov8-pose 和 yolov8-obb

前面博文【opencv dnn模块 示例(23) 目标检测 object_detection 之 yolov8】 已经已经详细介绍了yolov8网络和测试。本文继续说明使用yolov8 进行 人体姿态估计 pose 和 旋转目标检测 OBB 。 文章目录 1、Yolov8-pose 简单使用2、Yolov8-OBB2.1、python 命令行测试2.2、opencv…

css clip-path polygon属性实现直角梯形

2024.3.8今天我学习了如何用css实现直角梯形的效果&#xff0c; 效果&#xff1a; 具体实现原理&#xff1a; 一、需要三个div&#xff1a; 外面一个大的div&#xff0c;里面左右两个小的div 我们需要先把第一个div变成直角梯形&#xff1a; 大概是这样&#xff0c;设置好之…

visual studio 将编译后的dll等文件自动复制到指定目录

编译后的文件dll等总要手动复制到指定目录下&#xff0c;为了解决这一繁琐的操作&#xff0c;可以直接设置在编译完成后&#xff0c;自动复制到目标目录 - 在解决方案资源管理器&#xff0c;选中项目右键-》选中属性-》在弹出的面板选择生成事件 - 在后期生成事件命令行里填写…

PCM会重塑汽车OTA格局吗(1)

目录 1.汽车OTA概述 2.ST如何考虑OTA&#xff1f; 2.1 Stellar四大亮点 2.2 PCM技术视角下的OTA 3.小结 1.汽车OTA概述 随着智能网联汽车的飞速发展&#xff0c;汽车OTA也越来越盛行&#xff1b; 目前来讲OTA分为FOTA和SOTA(Software-over-the-air)两种&#xff0c;区别…

【博士每天一篇文献-综述】Modular Brain Networks

阅读时间&#xff1a;2023-11-27 1 介绍 年份&#xff1a;2016 作者&#xff1a;Olaf Sporns&#xff0c;Richard Betzel&#xff0c;印第安纳大学心理与脑科学杰出教授 期刊&#xff1a; Annual review of psychology 引用量&#xff1a;1205 详细介绍了模块化大脑网络及其如…

UE5 UE4 开发常用工具AssetDeveTool

AssetDeveTool工具&#xff0c;支持UE5 5.0-.5.3 UE4 4.26/4.27 下载链接&#xff1a; 面包多 https://mbd.pub/o/bread/ZZubkphu 工坊&#xff1a; https://gf.bilibili.com/item/detail/1104960041 包含功能&#xff1a; 自动化批量展UV功能 快速选择功能 自动化批量减面功能…

京津冀光伏展

京津冀光伏展是一个旨在推动京津冀地区光伏产业发展的展览会。光伏产业是指利用太阳能光电转换技术&#xff0c;将太阳能转化为电能的产业。京津冀地区是中国重要的经济区域&#xff0c;也是光伏产业发展潜力很大的地区之一。京津冀光伏展为光伏企业提供了一个展示产品和技术的…

Springboot+vue的物业管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的物业管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的物业管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff…

【MapReduce】03.MapReduce框架原理

目录 1.InputFormat数据输入 1.1.切片与MapTask并行度决定机制 1.2.Job提交流程源码和切片源码 1.3.FileInputFormat切片机制 1.4.TextInputFormat 1.5.CombineTextInputFormat切片机制 1.6.CombineTextInputFormat 1.InputFormat数据输入 1.1.切片与MapTask并行度决定…

CSS盒子模型笔记

尚硅谷学习视频链接&#xff1a;117_CSS_盒子模型的组成部分_哔哩哔哩_bilibili 1、盒子组成 盒子组成 content内容 padding border &#xff08;margin不包含在盒子内&#xff09; 2、div样式width、height 当css3属性box-sizingcontent-box&#xff08;默认&#xff0…

0-hackbar最新版本(2.3.1)工具安装(超详细)

通过火狐搜索安装后&#xff0c;是需要收费的&#xff0c;获取url都是困难的 打开火狐浏览器右上角的三个横线-拓展和主题 百度界面按F12后的提示 修改过程&#xff1a; 按照如上一步步找到对应的文件&#xff0c;拖到桌面上 是一个xpi文件&#xff0c;以打开压缩包的方式打开…