当前位置: 首页 > news >正文

springboot整合pi支付开发

pi支付流程图:

  1. 使用Pi SDK功能发起支付
  2. 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出批准 API 请求)
  3. 从您的应用程序服务器到 Pi 服务器的 API 请求以批准付款(让 Pi 服务器知道您知道此付款)
  4. Pi浏览器向用户显示付款详细信息页面,我们正在等待用户签署交易
  5. 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出完整的 API 请求)
  6. 从您的应用服务器到 Pi 服务器的 API 请求以完成付款(让 Pi 服务器知道您已完成此付款)

支付流程

 引入依赖

  <dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.10.0-RC1</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0.M4</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency>

配置api密钥

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;/*** 服务器端key* @author ThinkPad*/
@Configuration
@Data
public class CommonConfig {@Value("${sdk.serverAccessKey}")private String serverAccessKey;
}

 接收和返回数据对象

loginVO接收pi中心来的用户信息

import lombok.Data;/*** @author wzx* 登录数据封装类*/
@Data
public class LoginVO {private String userId;private String userName;private String accessToken;
}

paymentVO接收支付授权的信息

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;/*** @author ThinkPad*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentVO {private String paymentId;// 交易金额private BigDecimal amount;// 名片对应的用户数据private String shopUserId;// 商品idprivate String shopId;// 当前账号用户的idprivate String userId;}

completeVO接收支付完成的信息

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author ThinkPad*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CompleteVO {// PI支付IDprivate String paymentId;// txIdprivate String txId;// 订单ID【余额支付参数】private String orderId;// 支付方式:0:PI钱包 1:余额支付private String payType;
}

incompleteVO接收未完成订单的信息

/*** @author ThinkPad*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IncompleteVO {private String identifier;private TransactionVO transaction;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author ThinkPad*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TransactionVO {private String txid;private String _link;
}

工具类

发起http请求工具类

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;/*** @author Ashy.Cheung* @http 请求工具类* @date 2017.11.10*/
public class HttpClientUtil {public static String sendGet(String url) {CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpget = new HttpGet(url);CloseableHttpResponse response = null;try {response = httpclient.execute(httpget);} catch (IOException e1) {e1.printStackTrace();}String result = null;try {HttpEntity entity = response.getEntity();if (entity != null) {result = EntityUtils.toString(entity);}} catch (ParseException | IOException e) {e.printStackTrace();} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}return result;}/**** @param url* @param charsetName 返回字符集* @return*/public static String sendGet(String url, String charsetName) {InputStream inputStream = null;HttpURLConnection urlConnection = null;try {URL url1 = new URL(url);urlConnection = (HttpURLConnection) url1.openConnection();// 将返回的输入流转换成字符串inputStream = urlConnection.getInputStream();// 指定编码格式if (StringUtils.isBlank(charsetName)) {charsetName = "UTF-8";}InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);BufferedReader in = new BufferedReader(inputStreamReader);String jsonUserStr = in.readLine();return jsonUserStr;} catch (Exception e) {throw new RuntimeException(e);} finally {try {if (null != inputStream) {inputStream.close();}urlConnection.disconnect();} catch (Exception e) {}try {if (null != urlConnection) {urlConnection.disconnect();}} catch (Exception e) {}}}/*** 发送HttpPost请求,参数为String* 接收端以流形式接收*/public static String sendPost(String url, String param) {CloseableHttpClient httpclient = HttpClients.createDefault();StringEntity strEntity = null;try {strEntity = new StringEntity(param, "UTF-8");strEntity.setContentType("application/json");} catch (Exception e1) {e1.printStackTrace();}HttpPost httppost = new HttpPost(url);httppost.setEntity(strEntity);CloseableHttpResponse response = null;String result = null;try {response = httpclient.execute(httppost);HttpEntity entity1 = response.getEntity();result = EntityUtils.toString(entity1);} catch (IOException e) {//  e.printStackTrace();} finally {try {response.close();} catch (Exception e) {}}return result;}/*** 发送不带参数的HttpPost请求*/public static String sendPost(String url) {CloseableHttpClient httpclient = HttpClients.createDefault();HttpPost httppost = new HttpPost(url);CloseableHttpResponse response = null;try {response = httpclient.execute(httppost);} catch (IOException e) {e.printStackTrace();}HttpEntity entity = response.getEntity();String result = null;try {result = EntityUtils.toString(entity);} catch (ParseException | IOException e) {e.printStackTrace();} finally {try {response.close();} catch (Exception e) {}}return result;}}

 分布式锁工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
@Component
public class RedisLockUtil {private static final Logger log = LoggerFactory.getLogger(RedisLockUtil.class);@ResourceRedisTemplate<String, Object> redisTemplate;/*** 释放锁脚本,原子操作,lua脚本*/private static final String UNLOCK_LUA;static {StringBuilder sb = new StringBuilder();sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");sb.append("then ");sb.append("    return redis.call(\"del\",KEYS[1]) ");sb.append("else ");sb.append("    return 0 ");sb.append("end ");UNLOCK_LUA = sb.toString();}/*** 获取分布式锁,原子操作** @param lockKey   锁* @param lockValue 唯一ID* @param leaseTime 过期时间 秒* @return 是否枷锁成功*/public  boolean tryLock(String lockKey, String lockValue, long leaseTime) {try {RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),RedisStringCommands.SetOption.SET_IF_ABSENT);return redisTemplate.execute(callback);} catch (Exception e) {log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);}return false;}/*** 释放锁** @param lockKey   锁* @param lockValue 唯一ID* @return 执行结果*/public  boolean unlock(String lockKey, String lockValue) {RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8));return redisTemplate.execute(callback);}/*** 获取分布式锁,该方法不再使用** @param lockKey   锁* @param lockValue 唯一ID* @param waitTime  等待时间 秒* @param leaseTime 过期时间 秒* @return 是否枷锁成功*/@Deprecatedpublic   boolean tryLock(String lockKey, String lockValue, long waitTime, long leaseTime) {try {RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),RedisStringCommands.SetOption.SET_IF_ABSENT);return redisTemplate.execute(callback);} catch (Exception e) {log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);}return false;}
}

生成uuid工具类

import java.text.SimpleDateFormat;
import java.util.Date;public class UUID {public static String randomUUID() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HH'H'mm'M'ss'S'SSS");String id = sdf.format(new Date()) + (int) ((Math.random() * 9 + 1) * 100000000) + (int) ((Math.random() * 9 + 1) * 10);return id;}public static String randomQr() {String id = (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000);return id;}}

信息返回的枚举

import lombok.AllArgsConstructor;import lombok.Getter;/*** @author ThinkPad*/@Getter
@AllArgsConstructor
public enum PaymentEnum {PAYMENT_ENUM_1(1, "订单不存在","失败"),PAYMENT_ENUM_2(2,"订单不是待支付状态","失败"),PAYMENT_ENUM_3(3,"支付金额少于订单金额","失败"),PAYMENT_ENUM_4(4,"调用太快","失败"),PAYMENT_ENUM_5(5,"余额不足,请前往充值","失败"),PAYMENT_ENUM_6(6,"支付成功","成功"),PAYMENT_ENUM_7(7,"处理成功","失败"),PAYMENT_ENUM_8(8,"处理失败","失败");private final Integer code;private final String msg;private final String status;public static String getMsgByCode(Integer code) {for (PaymentEnum value : PaymentEnum.values()) {if (value.getCode().equals(code)) {return value.getMsg();}}return null;}public static String getStatusByCode(Integer code) {for (PaymentEnum value : PaymentEnum.values()) {if (value.getCode().equals(code)) {return value.getStatus() ;}}return null;}}

支付的controller层

 /*** 处理未完成的订单 (这部十分重要,会影响到后面的操作)*/@PostMapping("payOrder/incomplete")@ApiOperation("处理未完成的订单")@ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")public ResponseVO incomplete(@RequestBody IncompleteVO incompleteVO) {try {return orderInfoService.incomplete(incompleteVO);} catch (Exception e) {log.error("报错如下:{}", e.getMessage());throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}}/*** 前端请求支付授权,在本地订单创建后调*/@PostMapping("payOrder/approve")@ApiOperation("前端请求支付授权,在本地订单创建后调")@ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")public ResponseVO<String> approve(@RequestBody PaymentVO paymentVO) {try {String orderId = orderInfoService.approve(paymentVO);return ResponseVO.getSuccessResponseVo(orderId);} catch (Exception e) {log.error("报错如下:{}", e.getMessage());throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}}/*** 前端支付完成,余额支付直接调用此方法*/@PostMapping("payOrder/complete")@ApiOperation("前端支付完成,余额支付直接调用此方法")@ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")public ResponseVO complete(@RequestBody CompleteVO completeVO) {try {return orderInfoService.complete(completeVO);} catch (Exception e) {log.error("报错如下:{}", e.getMessage());throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}}/*** 取消支付,订单关闭*/@PostMapping("payOrder/cancelled")@ApiOperation("取消支付,订单关闭")@ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")public ResponseVO<String> cancelled(@RequestBody String orderId) {try {Boolean order = orderInfoService.cancelled(orderId);
//            if (!order){throw  new BusinessException("取消订单失败");}return ResponseVO.getSuccessResponseVo("取消订单成功");} catch (Exception e) {log.error("报错如下:{}", e.getMessage());throw new BusinessException("取消失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}}

支付的service层

    /*** 请求支付授权,创建order。返回orderId* @param paymentVO* @return*/@Override@Transactional(rollbackFor = Exception.class)public String approve(PaymentVO paymentVO) {log.error("approve-------------------------------------------------------------");OrderInfo orderInfo;log.error("paymentVO----------------------------------"+paymentVO);//获取付款信息OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId()).addHeader("Authorization", "Key " + commonConfig.getServerAccessKey()).build();try (Response response = client.newCall(request).execute()) {if (!response.isSuccessful()) {String string = response.body().string();JSONObject jsonObject1 = JSON.parseObject(string);log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));}String string = response.body().string();log.error("response-------------------------------------------------------------"+string);JSONObject jsonObject1 = JSON.parseObject(string);//校验实际支付金额BigDecimal userFinalPrice = paymentVO.getAmount();if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));throw new RuntimeException("支付金额少于订单金额");}} catch (Exception e) {throw  new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}OkHttpClient client1 = new OkHttpClient();//信息真实,通知PI我准备好了,可以付款了Request request1 = new Request.Builder().url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve").addHeader("Content-Type", "application/json").addHeader("Access-Control-Allow-Origin", "*").addHeader("Authorization", "Key " + commonConfig.getServerAccessKey()).post(RequestBody.create("", MediaType.parse("application/json"))).build();try (Response response1 = client1.newCall(request1).execute()) {if (!response1.isSuccessful()) {throw new RuntimeException("approve error: ");}log.error("response1-------------------------------------------------------------");//更新支付报文
//                    tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
//                    tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
//                    itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);log.error("return-------------------------------------------------------------");} catch (RuntimeException | IOException e) {log.error("error-------------------------------------------------------------");e.printStackTrace();}// 生成订单orderInfo = new OrderInfo();orderInfo.setOrderId(StringUtil.generateShortId());orderInfo.setShopId(paymentVO.getShopId());orderInfo.setUserId(paymentVO.getUserId());orderInfo.setShopUserId(paymentVO.getShopUserId());orderInfo.setAmount(paymentVO.getAmount());orderInfoMapper.insert(orderInfo);log.error("生成订单-------------------------------------------------------------");return orderInfo.getOrderId();}/*** 前端支付完成,余额支付直接调用此方法*/@Override@Transactional(rollbackFor = Exception.class)public ResponseVO complete(CompleteVO completeVO) {String payType = completeVO.getPayType();log.error("complete------------------------------------------------------------"+completeVO);if ("1".equals(payType)) {//余额支付String orderId = completeVO.getOrderId();String lockName = "access:lock:complete:" + orderId;String lockId = UUID.randomUUID();if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {// 调用太快return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));}// 获取订单信息OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", orderId));if ((orderInfo.getOrderStatus() != 0)) {// 订单不是待支付状态return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));}String userId = orderInfo.getUserId();AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper<AccountInfo>().eq("user_id", userId));BigDecimal balance = accountInfo.getPiBalance();if (balance.compareTo(orderInfo.getAmount()) < 0) {// 余额不足,请前往充值return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));}int update = orderInfoMapper.update(null,new UpdateWrapper<OrderInfo>().eq("order_id",orderId).set("order_status",1));balance=balance.subtract(orderInfo.getAmount());int update1 = accountInfoMapper.update(null, new UpdateWrapper<AccountInfo>().eq("user_id", userId).set("pi_balance", balance));// 支付成功return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));}//PI钱包支付String paymentId = completeVO.getPaymentId();//PI订单号String lockName = "access:lock:complete:" + paymentId;String lockId = UUID.randomUUID();log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {// 调用太快log.error("!RedisLockUtil---------------------------------------------------------调用太快");return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));}OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", completeVO.getOrderId()));log.error("orderId--------------------------------------------------------------"+orderInfo);if (null == orderInfo) {// 订单不存在log.error("!orderinfo--------------------------------------------------------不存在");return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));}log.error("orderinfo------------------------------------------------------------------"+orderInfo);if (orderInfo.getOrderStatus() != 0) {// 订单不是待支付状态log.error("!order---------------------------------------------------------pay");return  new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));}//通知PI完成交易JSONObject jsonObject = new JSONObject();jsonObject.put("txid", completeVO.getTxId());Map<String, String> heads = new HashMap<>();heads.put("Content-Type", "application/json;charset=UTF-8");heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());log.error("pi-----------------------------------------"+jsonObject);try {HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete").headerMap(heads, false).body(String.valueOf(jsonObject)).timeout(5 * 60 * 1000).execute();String body = response.body();JSONObject jsonObject1 = JSON.parseObject(body);String error = jsonObject1.getString("error");if (!StringUtils.isEmpty(error)) {log.error("!strinutils-----------------------------"+body);throw new RuntimeException("订单完成异常!");}orderInfo.setOrderStatus(1);// 更新订单orderInfoMapper.updateById(orderInfo);log.error("支付成功------------------------------------------------------");// 支付成功return  new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));} catch (Exception e) {throw e;}}@Override@Transactional(rollbackFor = Exception.class)public Boolean cancelled(String orderId) {int update = orderInfoMapper.update(null, new UpdateWrapper<OrderInfo>().eq("order_id", orderId).set("order_status", 3));return update > 0;}@Overridepublic ResponseVO incomplete(IncompleteVO incompleteVO) {log.error("incomplete--------------------------------");try {//先处理未完成的订单String oldpaymentId = incompleteVO.getIdentifier();TransactionVO transaction = incompleteVO.getTransaction();log.error("?transation--------------------"+transaction);log.error("?oldpaymentId------------------"+oldpaymentId);if (null != transaction) {log.error("transation--------------------"+transaction);log.error("oldpaymentId------------------"+oldpaymentId);String txid = transaction.getTxid();String txURL = transaction.get_link();//                OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", oldpaymentId));
//                if (null == orderInfo) {
//                    log.error("order-----------------null");
//                    throw new RuntimeException("旧订单不存在");
//                }
//
//                if (orderInfo.getOrderStatus()==1) {
//                    log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
//                    throw new RuntimeException("订单是已支付状态");
//                }String get = HttpClientUtil.sendGet(txURL);JSONObject jsonObject1 = JSON.parseObject(get);String piOrderId = jsonObject1.getString("memo");//我方订单IDlog.error("memo---------------------"+piOrderId);JSONObject jsonObject = new JSONObject();jsonObject.put("txid", txid);Map<String, String> heads = new HashMap<>();heads.put("Content-Type", "application/json;charset=UTF-8");heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());try {HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete").headerMap(heads, false).body(String.valueOf(jsonObject)).timeout(5 * 60 * 1000).execute();String body = response.body();JSONObject jsonObject2 = JSON.parseObject(body);String error = jsonObject2.getString("error");if (!StringUtils.isEmpty(error)) {log.error("!response------------------"+error);throw new RuntimeException("订单完成异常!");}return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));} catch (Exception e) {throw e;}}} catch (Exception e) {return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));}return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));}

 前端代码

前端路由 

API
// 授权
import request from "@/api/http";
import axios from "axios";// 支付授权
export function payAuth(data){return request({url: "/api/order/payOrder/approve",method: "post",data,});
}//未完成订单
export function payIncomplete(data){return request({url: "/api/order/payOrder/incomplete",method: "post",data,});
}//支付成功
export function payDone(data){return request({url: "/api/order/payOrder/complete",method: "post",data,});
}
//支付取消
export function payCancel(data){return request({url: "/api/order/payOrder/cancelled",method: "post",data,})
}

支付前端代码

 test(){const pay_message = this.pay_messagelet orderid = ''console.log({amount: pay_message.amount,shopUserId: pay_message.shopUserId,shopId: pay_message.shopId,userId: pay_message.userId})Pi.createPayment({// Amount of π to be paid:amount: 3.14,// An explanation of the payment - will be shown to the user:memo: "购买特殊数据", // e.g: "Digital kitten #1234",// An arbitrary developer-provided metadata object - for your own usage:metadata: { productID : 'apple_pie_1'  }, // e.g: { kittenId: 1234 }}, {// Callbacks you need to implement - read more about those in the detailed docs linked below:// 授权async onReadyForServerApproval(paymentId) {console.log('paymentId',paymentId)payAuth({paymentId: paymentId,amount: pay_message.amount,shopUserId: pay_message.shopUserId,shopId: pay_message.shopId,userId: pay_message.userId}).then((data) => {orderid = data.dataconsole.log('orderId',orderid)}).catch(error => {console.log(error)})},//支付成功onReadyForServerCompletion: function(paymentId, txid) {alert(1111)console.log(paymentId, 'paymentId', 'txid', txid,'orderid',orderid )payDone({paymentId: paymentId, txId: txid, orderId: orderid,payType:'0'}).then(res => {console.log(res)// if (res && res.code === 0) {//   this.payDoneJump();// }})},//支付取消onCancel: function(orderid) {console.log('onCancel' + orderid)payCancel(orderid).then((data) => {console.log(data)})},//支付失败onError: function(error, payment) {console.log('error:',error);console.log('payment:',payment)}});},

登录自动调用未支付订单,这个十分重要因为会影响支付授权。 

const loginFun = () => {Pi.init({ version: "2.0", sandbox: true });const scopes = ["payments", "username", "wallet_address"];function onIncompletePaymentFound(payment) {alert(1111111)console.log("payment", payment);return payIncomplete({identifier:payment.identifier,transaction:{_link:payment.transaction._link,txid:res.transaction.txid}})}Pi.authenticate(scopes, onIncompletePaymentFound).then(function (auth) {console.log("auth", auth);let userInfo = {accessToken: auth.accessToken,userId: auth.user.uid,userName: auth.user.username,};// userGetPush().then((data) => {//   console.log(data);//   userStore().userGetPush = data.data;// });Login(userInfo).then((data) => {console.log(data);if (data.status == "success") {// 将用户信息存入piniauserStore().userInfoChange(data.data);// 发布消息到socket Login() 存入userId// this.$socket.emit("login", data.data.userInfo.userId);router.push("/home");}});}).catch(function (error) {console.error(error);});
};

详细流程

使用Pi-SDK功能发起支付

 由Pi SDK自动调用的回调函数,发出支付批准请求

 

 路由到后端的支付授权接口

 后端服务器向Pi服务器发起支付授权

 @Override@Transactional(rollbackFor = Exception.class)public String approve(PaymentVO paymentVO) {log.error("approve-------------------------------------------------------------");OrderInfo orderInfo;log.error("paymentVO----------------------------------"+paymentVO);//获取付款信息OkHttpClient client = new OkHttpClient();Request request = new Request.Builder().url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId()).addHeader("Authorization", "Key " + commonConfig.getServerAccessKey()).build();try (Response response = client.newCall(request).execute()) {if (!response.isSuccessful()) {String string = response.body().string();JSONObject jsonObject1 = JSON.parseObject(string);log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));}String string = response.body().string();log.error("response-------------------------------------------------------------"+string);JSONObject jsonObject1 = JSON.parseObject(string);//校验实际支付金额BigDecimal userFinalPrice = paymentVO.getAmount();if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));throw new RuntimeException("支付金额少于订单金额");}} catch (Exception e) {throw  new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());}OkHttpClient client1 = new OkHttpClient();//信息真实,通知PI我准备好了,可以付款了Request request1 = new Request.Builder().url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve").addHeader("Content-Type", "application/json").addHeader("Access-Control-Allow-Origin", "*").addHeader("Authorization", "Key " + commonConfig.getServerAccessKey()).post(RequestBody.create("", MediaType.parse("application/json"))).build();try (Response response1 = client1.newCall(request1).execute()) {if (!response1.isSuccessful()) {throw new RuntimeException("approve error: ");}log.error("response1-------------------------------------------------------------");//更新支付报文
//                    tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
//                    tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
//                    itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);log.error("return-------------------------------------------------------------");} catch (RuntimeException | IOException e) {log.error("error-------------------------------------------------------------");e.printStackTrace();}// 生成订单orderInfo = new OrderInfo();orderInfo.setOrderId(StringUtil.generateShortId());orderInfo.setShopId(paymentVO.getShopId());orderInfo.setUserId(paymentVO.getUserId());orderInfo.setShopUserId(paymentVO.getShopUserId());orderInfo.setAmount(paymentVO.getAmount());orderInfoMapper.insert(orderInfo);log.error("生成订单-------------------------------------------------------------");return orderInfo.getOrderId();}

PI游览器向用户显示付款详细信息页面,我们等待用户签署交易

 由Pi SDK自动调用完成的回调函数

 从你的后端服务器到Pi服务器的API请求以完成付款(让pi服务器知道你完成此付款)

 /*** 前端支付完成,余额支付直接调用此方法*/@Override@Transactional(rollbackFor = Exception.class)public ResponseVO complete(CompleteVO completeVO) {String payType = completeVO.getPayType();log.error("complete------------------------------------------------------------"+completeVO);if ("1".equals(payType)) {//余额支付String orderId = completeVO.getOrderId();String lockName = "access:lock:complete:" + orderId;String lockId = UUID.randomUUID();if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {// 调用太快return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));}// 获取订单信息OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", orderId));if ((orderInfo.getOrderStatus() != 0)) {// 订单不是待支付状态return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));}String userId = orderInfo.getUserId();AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper<AccountInfo>().eq("user_id", userId));BigDecimal balance = accountInfo.getPiBalance();if (balance.compareTo(orderInfo.getAmount()) < 0) {// 余额不足,请前往充值return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));}int update = orderInfoMapper.update(null,new UpdateWrapper<OrderInfo>().eq("order_id",orderId).set("order_status",1));balance=balance.subtract(orderInfo.getAmount());int update1 = accountInfoMapper.update(null, new UpdateWrapper<AccountInfo>().eq("user_id", userId).set("pi_balance", balance));// 支付成功return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));}//PI钱包支付String paymentId = completeVO.getPaymentId();//PI订单号String lockName = "access:lock:complete:" + paymentId;String lockId = UUID.randomUUID();log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {// 调用太快log.error("!RedisLockUtil---------------------------------------------------------调用太快");return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));}OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", completeVO.getOrderId()));log.error("orderId--------------------------------------------------------------"+orderInfo);if (null == orderInfo) {// 订单不存在log.error("!orderinfo--------------------------------------------------------不存在");return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));}log.error("orderinfo------------------------------------------------------------------"+orderInfo);if (orderInfo.getOrderStatus() != 0) {// 订单不是待支付状态log.error("!order---------------------------------------------------------pay");return  new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));}//通知PI完成交易JSONObject jsonObject = new JSONObject();jsonObject.put("txid", completeVO.getTxId());Map<String, String> heads = new HashMap<>();heads.put("Content-Type", "application/json;charset=UTF-8");heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());log.error("pi-----------------------------------------"+jsonObject);try {HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete").headerMap(heads, false).body(String.valueOf(jsonObject)).timeout(5 * 60 * 1000).execute();String body = response.body();JSONObject jsonObject1 = JSON.parseObject(body);String error = jsonObject1.getString("error");if (!StringUtils.isEmpty(error)) {log.error("!strinutils-----------------------------"+body);throw new RuntimeException("订单完成异常!");}orderInfo.setOrderStatus(1);// 更新订单orderInfoMapper.updateById(orderInfo);log.error("支付成功------------------------------------------------------");// 支付成功return  new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));} catch (Exception e) {throw e;}}

注意,如果用户有未处理的订单,会导致用户重新创建支付失败,需要有个接口去处理未完成的订单

前端一初始化就去执行处理未完成的订单

路由到后端的接口 

 

 后端接口代码

@Overridepublic ResponseVO incomplete(IncompleteVO incompleteVO) {log.error("incomplete--------------------------------");try {//先处理未完成的订单String oldpaymentId = incompleteVO.getIdentifier();TransactionVO transaction = incompleteVO.getTransaction();log.error("?transation--------------------"+transaction);log.error("?oldpaymentId------------------"+oldpaymentId);if (null != transaction) {log.error("transation--------------------"+transaction);log.error("oldpaymentId------------------"+oldpaymentId);String txid = transaction.getTxid();String txURL = transaction.get_link();//                OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", oldpaymentId));
//                if (null == orderInfo) {
//                    log.error("order-----------------null");
//                    throw new RuntimeException("旧订单不存在");
//                }
//
//                if (orderInfo.getOrderStatus()==1) {
//                    log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
//                    throw new RuntimeException("订单是已支付状态");
//                }String get = HttpClientUtil.sendGet(txURL);JSONObject jsonObject1 = JSON.parseObject(get);String piOrderId = jsonObject1.getString("memo");//我方订单IDlog.error("memo---------------------"+piOrderId);JSONObject jsonObject = new JSONObject();jsonObject.put("txid", txid);Map<String, String> heads = new HashMap<>();heads.put("Content-Type", "application/json;charset=UTF-8");heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());try {HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete").headerMap(heads, false).body(String.valueOf(jsonObject)).timeout(5 * 60 * 1000).execute();String body = response.body();JSONObject jsonObject2 = JSON.parseObject(body);String error = jsonObject2.getString("error");if (!StringUtils.isEmpty(error)) {log.error("!response------------------"+error);throw new RuntimeException("订单完成异常!");}return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));} catch (Exception e) {throw e;}}} catch (Exception e) {return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));}return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));}

相关文章:

springboot整合pi支付开发

pi支付流程图&#xff1a; 使用Pi SDK功能发起支付由 Pi SDK 自动调用的回调函数&#xff08;让您的应用服务器知道它需要发出批准 API 请求&#xff09;从您的应用程序服务器到 Pi 服务器的 API 请求以批准付款&#xff08;让 Pi 服务器知道您知道此付款&#xff09;Pi浏览器向…...

类 ChatGPT 模型存在的局限性

尽管类ChatGPT模型经过数月的迭代和完善&#xff0c;已经初步融入了部分领域以及人们的日常生活&#xff0c;但目前市面上的产品和相关技术仍然存在一些问题&#xff0c;以下列出一些局限性进行详细说明与成因分析&#xff1a; 1&#xff09;互联网上高质量、大规模、经过清洗…...

Nginx的安全控制

安全控制 关于web服务器的安全是比较大的一个话题&#xff0c;里面所涉及的内容很多&#xff0c;Nginx反向代理是安全隔离来提升web服务器的安全&#xff0c;通过代理分开了客户端到应用程序服务器端的连接&#xff0c;实现了安全措施。在反向代理之前设置防火墙&#xff0c;…...

字符串与字符编码 - GO语言从入门到实战

字符串与字符编码 - GO语言从入门到实战 字符串 与其他主要编程语⾔的差异 基本数据类型&#xff1a;string 是基础数据类型&#xff0c;而不是引用类型或指针类型。string 在内存中占用的空间大小是固定的&#xff0c;且只读、不可改变。字节切片&#xff1a;string 是只读…...

12P4375X042-233C KJ2005X1-BA1 CE3007 EMERSON servo controller

12P4375X042-233C KJ2005X1-BA1 CE3007 EMERSON servo controller 我们提供三种不同类别的EDGEBoost I/O模块供选择&#xff0c;以实现最大程度的I/O定制: 数字和模拟输入/输出网络和连接边缘人工智能和存储 利用EDGEBoost I/O实现变革性技术 EBIO-2M2BK EBIO-2M2BK载板支持…...

WPF向Avalonia迁移(四、其他事项)

开发必备 1. Avalonia项目源代码&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;没有源代码&#xff0c;你连控件的背景色怎么改都找不着&#xff01;&#xff01; 2.下载你所使用的版本&#x…...

Python 代码调试

from pdb import set_trace as stx 是一个Python代码中常用的调试技巧之一&#xff0c;它用于在代码中插入断点以进行调试。这行代码的作用是将Python标准库中的 pdb&#xff08;Python Debugger&#xff09;模块中的 set_trace 函数导入&#xff0c;并将其重命名为 stx&#x…...

DM宣传单制作,利用在线模板,快速替换文字

如果你需要制作一批宣传单&#xff0c;但是时间很紧&#xff0c;而且没有专业的设计人员协助&#xff0c;那么你可以选择使用在线模板来快速制作宣传单。本文将介绍如何使用乔拓云平台&#xff0c;快速制作宣传单的方法。 步骤一&#xff1a;选择适合的在线制作工具 首先&…...

【力扣】42. 接雨水

这道题我卡了差不多1个小时&#xff0c;不是不会做&#xff0c;是不知道怎么能用栈来实现&#xff0c;后面看了一个博主的视频&#xff0c;豁然开朗&#xff0c;我主要的纠结点在于当指针指到7的时候&#xff0c;我计算出4到7的水块是2&#xff0c;但实际上是0&#xff0c;因为…...

IPETRONIK数据采集设备携手Softing Q-Vision软件致力于ADAS测试方案

一 背景 汽车ADAS技术是当下国内外的重点研究方向&#xff0c;且ADAS的发展水平和市场竞争力紧密相关&#xff0c;因此一套完善的ADAS测试方案对各整车厂而言非常重要。然而&#xff0c;国内ADAS测试却面临着很多阻碍&#xff0c;主要原因在于&#xff1a;相关测试设备昂贵&am…...

Go语言中的指针介绍

Go语言中的指针 文章目录 Go语言中的指针一、Go语言中的指针介绍1.1 指针介绍1.2 基本语法1.3 声明和初始化1.4 Go 指针的3个重要概念1.4.1 指针地址&#xff08;Pointer Address&#xff09;1.4.2 指针类型&#xff08;Pointer Type&#xff09;1.4.3 指针取值&#xff08;Poi…...

简单理解区块链

这篇是挖矿篇详细介绍区块链之挖矿-CSDN博客的后置文章&#xff0c;咱们通过之前的解释进一步复习学习区块链叭&#xff01; 百度百科定义 区块链&#xff0c;就是一个又一个区块组成的链条。每一个区块中保存了一定的信息&#xff0c;它们按照各自产生的时间顺序连接成链条。这…...

[尚硅谷React笔记]——第3章 React应用(基于React脚手架)

目录&#xff1a; react脚手架创建项目并启动react脚手架项目结构一个简单的Hello组件样式的模块化功能界面的组件化编码流程&#xff08;通用&#xff09;组件的组合使用-TodoList 1.react脚手架 xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目 包含了所有需…...

《Linux 内核设计与实现》13. 虚拟文件系统

通用文件接口 VFS 使得可以直接使用 open()、read()、write() 这样的系统调用而无需考虑具体文件系统和实际物理介质。 好处&#xff1a;新的文件系统和新类型的存储介质需要挂载时&#xff0c;程序无需重写&#xff0c;甚至无需重新编译。 VFS 将各种不同的文件系统抽象后采…...

2021-06-09 51单片机:两个独立按键控制一个led,k1按下松开led闪烁三次,k2按下LED闪烁五次

缘由51单片机:两个独立按键控制一个led,k1按下松开led闪烁三次,k2按下LED闪烁五次_嵌入式-CSDN问答 #include "REG52.h" sbit K1 P1^0; sbit K2 P1^1; sbit LEDP0^0; void main() {unsigned char Xd0,ss0;unsigned int wei0;while(1){if(K10&&Xd0){ss3*2;…...

C/C++ 经典面试算法题

1.打印杨辉三角 1 #include <stdio.h>2 #include <string.h>3 4 int main()5 {6 int x;7 int a[100][100];8 printf("输入行数\n");9 scanf("%d",&x); 10 for(int i 0;i<x;i) 11 { 12 for(int j 0;…...

2023年下学期《C语言》作业0x02-分支 XTU OJ 1068 1069 1070 1071 1072

第一题 #include<stdio.h>int main() {int a;scanf("%d",&a);if(a>90&&a<100) printf("A");else printf("B");return 0; } 没有换行&#xff0c;不然会格式错误 第二题 #include<stdio.h>int main() {int a;s…...

JMeter学习第一、二、三天

首先&#xff0c;我们来了解一下到底什么是接口测试与性能测试&#xff1a; 接口测试 定义 接口测试主要关注系统组件之间的交互&#xff0c;确保各个接口按预期工作。这包括验证传递的数据、数据格式、调用的频率和其他与接口调用相关的任何限制。 目的 确保系统的各个组件可…...

常用的分布式ID解决方案原理解析

目录 前言 一&#xff1a;分布式ID的使用场景 二&#xff1a;分布式ID设计的技术指标 三&#xff1a;常见的分布式ID生成策略 3.1 UUID 3.2 数据库生成 3.3 数据库的多主模式 3.4 号段模式 3.5 雪花算法 前言 分布式ID的生成是分布式系统中非常核心的基础性模块&#…...

echarts3D地图打点

1、echarts地图打点加鼠标移上去显示文字 2、1-3和前面的一样echart3D地图 if (res.code 0) {const resData res.data || [];if (resData.length > 0) {for (var i 0; i < resData.length; i) {let arr new Array(2);arr[0] resData[i].longitude || ""…...

分布式主键算法

目录 一、引言二、常见算法介绍雪花算法&#xff08;Snowflake Algorithm&#xff09;特性详解优势劣势 UUID&#xff08;Universally Unique Identifier&#xff09;特性详解优势劣势 数据库自增主键特性详解优势劣势 分布式数据库的序列&#xff08;Sequence&#xff09;特性…...

暴力破解及验证码安全

1.暴力破解注意事项 1、破解前一定要有一个有郊的字典&#xff08;Top100 TOP2000 csdn QQ 163等密码&#xff09; https://www.bugku.com/mima/ 密码生成器 2、判断用户是否设置了复杂的密码 在注册页面注册一个,用简单密码看是否可以注册成功 3、网站是…...

程序无法启动,提示“找不到msvcp140.dll”或“msvcp140.dll缺失报错”解决方法

大家好&#xff01;今天我来给大家分享一下msvcp140.dll丢失的解决方法。我们都知道&#xff0c;在运行一些软件或游戏时&#xff0c;经常会遇到“找不到msvcp140.dll”的错误提示&#xff0c;这会让我们非常苦恼。那么&#xff0c;这个问题该怎么解决呢&#xff1f;下面我将为…...

【Python查找算法】二分查找、线性查找、哈希查找

目录 1 二分查找算法 2 线性查找算法 3 哈希查找算法 1 二分查找算法 二分查找&#xff08;Binary Search&#xff09;是一种用于在有序数据集合中查找特定元素的高效算法。它的工作原理基于将数据集合分成两半&#xff0c;然后逐步缩小搜索范围&#xff0c;直到找到目标元素…...

【MySQL实战45讲-基础篇】

基础篇 基础架构 MySQL的基本架构示意图&#xff1a;MySQL可以分为Server层和存储引擎层两部分。 Server层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖MySQL的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数学和加密函…...

asp.net core中间件预防防止xss攻击

using System; using System.Text.Json; using System.Text.Json.Serialization;namespace CommonUtils {/// <summary>/// newtonsoft的转化器/// 防止xss攻击/// </summary>public class AntiXssNewtonsoftConverter : Newtonsoft.Json.JsonConverter<string&…...

jvm概述

1、JVM体系结构 2、JVM运行时数据区 3、JVM内存模型 JVM运行时内存 共享内存区 线程内存区 3.1、共享内存区 共享内存区 持久带(方法区 其他) 堆(Old Space Young Space(den S0 S1)) 持久代&#xff1a; JVM用持久带&#xff08;Permanent Space&#xff09;实现方法…...

C++简单上手helloworld 以及 vscode找不到文件的可能性原因

helloworld #include <iostream>int main() {std::cout << "hello world!" << std::endl;return 0; }输入输出小功能 #include <iostream> using namespace std; /* *主函数 *输出一条语句 */int main() {// 输出一条语句cout << &q…...

掌动智能:性能压力测试的重要性

采用性能压力测试可以帮助企业预估系统容量、提升用户体验以及降低风险和成本。在软件开发过程中&#xff0c;将性能压力测试纳入测试策略的重要一环&#xff0c;将为企业的成功和用户满意度打下坚实的基础。 性能压力测试的重要性&#xff1a; 一、发现性能瓶颈 性能压力测试能…...

kafka日志文件详解及生产常见问题总结

一、kafka的log日志梳理 日志文件是kafka根目录下的config/server.properties文件&#xff0c;配置log.dirs/usr/local/kafka/kafka-logs&#xff0c;kafka一部分数据包含当前Broker节点的消息数据(在Kafka中称为Log日志)&#xff0c;称为无状态数据&#xff0c;另外一部分存在…...

双语 网站 数据怎么做/vue seo 优化方案

写成宏&#xff0c;方便移植#define setbit(x,y) x|(1<//将X的第Y位置1#define clrbit(x,y) x&~(1<方法二:C语言位运算除了可以提高运算效率外&#xff0c;在嵌入式系统的编程中&#xff0c;它的另一个最典型的应用&#xff0c;而且十分广泛地正在被使用着的是位间的…...

最新网站建设常见问题/百度官网网址

目录1 UV动画1.1 滑动表面着色器1.2 让UV流动1.3 流动方向1.4 定向滑动2 无缝循环2.1 混合权重2.2 跷跷板2.3 时间偏移2.4 结合两个不同的扭曲2.5 UV跳跃2.6 分析跳跃3 动画调整3.1 平铺3.2 动画速度3.3 流动强度3.4 流偏移4 纹理化4.1 抽象水纹4.2 法线贴图4.3 导数贴图4.4 高…...

网站开发制作公司简介/百度指数是什么

参考&#xff1a;http://www.cnblogs.com/denny402/p/5073427.html转载于:https://www.cnblogs.com/573177885qq/p/5805027.html...

兴仁企业建站公司/谷歌手机版下载安装

01. ip address show/ip a 检查网卡地址配置02. ping 测试网络连通性03. nmtui 图形界面修改网卡地址信息04. exit 注销05. shutdown 关机命令shutdown -h 5 指定关机时间 &#xff08;推荐&#xff09;shutdown -r 5 重启主机时间 &#xff08;推荐&#xff09;shutdown -…...

什么做网站统计好/重庆seo管理平台

原文&#xff1a;http://blog.csdn.net/jinzhencs/article/details/50930877 一.安装部署mongo 1.创建文件夹 /opt/mongodb/single /opt/mongodb/data/db 2.进入single目录下载安装包 //下载 tar.gz文件 wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.6.tgz …...

教育网站建设的素材/怎样进行seo推广

所有题目均有五种语言实现。C实现目录、C++ 实现目录、Python实现目录、Java实现目录、JavaScript实现目录...