企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充(Vue项目版)。。。

引子

关于企业微信PC版应用跳转到默认浏览器,我之前写过一篇文章:企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充。。。

以前的文章里用的前后端一体的Jsp项目,这次我使用的是前后端分离的Vue项目,这两者实现的方式差异较大,我在开发的过程中也碰到了不少问题,所以我再写一篇文章作为补充。

官方文档推荐是使用最新的WECOM-JSSDK,我刚开始也是用WECOM-JSSDK,但是碰到莫名其妙的问题,无法实现跳转(文章中有说明),最后不了了之。

后来我就采用了JS-SDK,最后成功实现跳转功能。

使用JS-SDK的前提是通过config接口注入权限验证配置。

在这里插入图片描述
在这里插入图片描述

然后调用openDefaultBrowser方法打开默认浏览器。

在这里插入图片描述

具体请查看企微微信客户端API文档。

实现思路

由于Vue项目采用前后端分离架构,客户端和服务器端通常部署在不同的服务器上。在企业微信上跳转到默认浏览器的时候,如何把服务器的token传递到客户端浏览器是个难点。

我想出来的实现思路是这样的:

  1. 在跳转到默认浏览器之前,先通过企业微信的OAuth2方式,获取服务器端的登录token
  2. 在跳转的URL后面加上token参数,传递到用默认浏览器打开的项目页面(storeTonken.vue)中
  3. 在项目页面(storeTonken.vue)中提取URL中token,保存到Cookie中,然后跳转到项目首页,实现单点登录

整体的流程图如下:
在这里插入图片描述

操作步骤

引入JS文件

首先在vue项目中的index.html中引入JS文件。

在这里插入图片描述

<script src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>

新建vue文件

然后在views文件夹下,新建一个文件夹“openDefaultBrowser”(名字任意取),然后新建一个index.vue和storeTonken.vue文件。

在这里插入图片描述
index.vue页面是跳转到默认浏览器用的,storeTonken.vue文件是保存token并跳转到首页用的。

配置白名单

然后在router中把这两个路径加入到白名单中,不用登录也能访问:

在这里插入图片描述

我这个项目还需要在permission.js中添加入白名单:

在这里插入图片描述

每个项目的架构不一样,请你根据实际情况配置。

需要注意的问题

设置可信域名

如应用页面需使用微信JS-SDK、跳转小程序等, 需完成域名归属验证。

就是在你的域名后面加上企业微信提供的这个 WW_verify***yo0iITyAeb6.txt 文件的URL,能够直接访问txt文件里面的内容。

如果你的域名是 abc.com,在浏览器中输入 http://abc.com/WW_verify***yo0iITyAeb6.txt,确保可以显示txt里面的内容。

在这里插入图片描述
那如何在服务器上设置呢?

我以nginx为例说一下步骤:

1、下载WW_verify***yo0iITyAeb6.txt 文件

2、把txt文件复制到服务器上,比如目录:/usr/local/etc 下

在这里插入图片描述
3、 在nginx.conf文件中添加 location = /WW_verify_Pmj1eyo0iITyAeb6.txt,记得把/usr/local/etc改成你自己的路径。

location = /WW_verify***yo0iITyAeb6.txt {
           alias /usr/local/etc/WW_verify***yo0iITyAeb6.txt;
           try_files $uri =404;
       }

4、重启nginx

最后我们来看下效果:

在这里插入图片描述
然后,点击验证,就可以验证通过了:

在这里插入图片描述

设置企业可信IP

记得要设置企业可信IP,不然会报:not allow to access from your ip 的错误。

在这里插入图片描述

Connection reset 问题

我把项目发布到正式服务器后,结果后台报 Connection reset 的异常:

在这里插入图片描述
这个应该是网络连接的问题,我在服务器上运行:curl https://qyapi.weixin.qq.com

果然报错了:

在这里插入图片描述
看来真的是网络问题。

后来发现是服务器的外网权限没开~

WECOM-JSSDK 报错:not be accessed on strict mode

我在使用WECOM-JSSDK的时候,前端会报错:not be accessed on strict mode,这个问题,后来一直没有解决。

不知道是不是这个错误导致浏览器跳转失败。

在这里插入图片描述

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
    at Arguments.invokeGetter (<anonymous>:3:28)
    at Object._getConfigSignature (http://localhost:9528/afterSale/app.js:24558:11)
    at Object.getConfigSignature (http://localhost:9528/afterSale/app.js:24538:38)
    at http://localhost:9528/afterSale/app.js:2714:49
    at step (http://localhost:9528/afterSale/app.js:2133:23)
    at Object.next (http://localhost:9528/afterSale/app.js:2074:20)
    at asyncGeneratorStep (http://localhost:9528/afterSale/app.js:2046:28)
    at _next (http://localhost:9528/afterSale/app.js:2061:17)
    at http://localhost:9528/afterSale/app.js:2066:13
    at new Promise (<anonymous>)

openDefaultBrowser\index.vue 使用WECOM-JSSDK的代码的版本:

<template>
  <div>
    正在跳转到默认浏览器...
  </div>
</template>

<script>
import { getWeiXinPermissionsValidationConfig } from '@/api/openDefaultBrowser'
import * as ww from '@wecom/jssdk'

export default {
  data() {
    return {
      paramObject: {
        url: '',
        type: ''
      }
    }
  },
  created() {
    this.getConfig()
  },
  methods: {
    // 获取相关验证配置信息
    getConfig() {
      // 该paramUrl 为你使用微信sdk-js相关接口的页面地址 该地址需要配置到应用后台的可信域名下
      const paramUrl = window.location.href.split('#')[0]
      this.paramObject.url = paramUrl
      this.companyConfigInit()
    },

    // 企业验证配置
    companyConfigInit() {
      const that = this
      getWeiXinPermissionsValidationConfig(that.paramObject).then(response => {
        ww.register({
          appId: response.data.corpid, // 必填,企业微信的corpID
          agentId: response.data.agentid, // 必填,当前应用的AgentID
          jsApiList: ['openDefaultBrowser'], // 你要调用的sdk接口必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
          getConfigSignature, // 必填,根据url生成企业签名的回调函数
          getAgentConfigSignature // 必填,根据url生成应用签名的回调函数
        })

        async function getConfigSignature() {
          console.log('calling getConfigSignature')
          // 根据 url 生成应用签名,生成方法同上,但需要使用应用登录授权的 jsapi_ticket
          return { timestamp: response.data.timestamp, nonceStr: response.data.nonceStr, signature: response.data.signature }
        }

        async function getAgentConfigSignature() {
          console.log('calling getAgentConfigSignature')
          return { timestamp: response.data.timestamp, nonceStr: response.data.nonceStr, signature: response.data.agentSignature }
        }

        ww.openDefaultBrowser({
          // 在默认浏览器打开redirect_uri,并附加code参数;也可以直接指定要打开的url,此时不会附带上code参数。
          url: 'https://work.weixin.qq.com/',
          success(result) {
            // 成功回调,result.errMsg 固定格式为“方法名:ok”
            console.log('success:' + result)
          },
          fail(result) {
            // 失败回调,通过 result.errMsg 查看失败详情
            console.log('fail:' + result)
          },
          complete(result) {
            // 完成回调,无论调用成功还是失败,都会回调该方法
            console.log('complete:' + result)
          }
        })
      })
    }
  }
}
</script>

后来实在解决不了,我就放弃使用WECOM-JSSDK了,转而使用JS-SDK,这段代码就算留个纪念吧。

如果你知道怎么解决,请得留言告诉我,谢谢。

项目代码

前端代码

openDefaultBrowser\index.vue

<template>
  <div>
    正在跳转到默认浏览器...
  </div>
</template>

<script>
import { weiXinLogin, getWeiXinPermissionsValidationConfig } from '@/api/openDefaultBrowser'
import { setToken } from '@/utils/auth'
const wx = window.wx

export default {
  data() {
    return {
      paramObject: {
        url: ''
      },
      token: ''
    }
  },
  created() {
    // 微信登录
    this.weiXinLogin()
  },
  methods: {
    // 微信登录
    weiXinLogin() {
      const url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=***&redirect_uri=http%3A%2F%2Fabc.com%3A8890%2FweixinLogin&response_type=code&scope=snsapi_userinfo&agentid=***&state=STATE#wechat_redirect'
      weiXinLogin(url).then(response => {
        console.log(response)
        this.token = response.data.token
        setToken(response.data.token)
        // 跳转到默认浏览器
        this.getConfig()
      })
    },
    // 获取相关验证配置信息
    getConfig() {
      // 该paramUrl 为你使用微信sdk-js相关接口的页面地址 该地址需要配置到应用后台的可信域名下
      const paramUrl = window.location.href.split('#')[0]
      this.paramObject.url = paramUrl
      this.companyConfigInit()
    },

    // 企业验证配置
    companyConfigInit() {
      const that = this
      getWeiXinPermissionsValidationConfig(that.paramObject).then(response => {
        wx.config({
          beta: true, // 必须这么写,否则wx.invoke调用形式的jsapi会有问题
          debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
          appId: response.data.corpid, // 必填,企业微信的corpid,必须与当前登录的企业一致
          timestamp: response.data.timestamp, // 必填,生成签名的时间戳
          nonceStr: response.data.nonceStr, // 必填,生成签名的随机串
          signature: response.data.signature, // 必填,签名,见附录-JS-SDK使用权限签名算法
          jsApiList: ['openDefaultBrowser'] // 必填,传入需要使用的接口名称
        })

        wx.ready(function() {
          that.openDefaultBrowser()
        })

        wx.error(function(res) {
          // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
          console.log(res)
        })
      })
    },
    openDefaultBrowser() {
      const that = this
      wx.invoke('openDefaultBrowser', {
        // 在默认浏览器打开的uri,附加token参数
        'url': 'http://abc.com:8889/***/#/storeTonken?token=' + that.token
      }, function(res) {
        console.log('res------------->', res)
        if (res.err_msg !== 'openDefaultBrowser:ok') {
          // 错误处理
        }
      })
    }
  }
}
</script>

以上代码中,通过 weiXinLogin 方法获取服务器的 token,先获取 token 然后再跳转到默认浏览器。

wx.config 中需要的参数通过 getWeiXinPermissionsValidationConfig 方法从服务器端获取,然后调用 openDefaultBrowser 方法实现默认浏览器跳转。

token通过url的参数传递:

'url': 'http://abc.com:8889/***/#/storeTonken?token=' + that.token

openDefaultBrowser.js

import request from '@/utils/request'

/**
 * 微信登录
 * @param {*} url
 * @returns
 */
export function weiXinLogin(url) {
  return request({
    url: url,
    method: 'get'
  })
}

/**
 * 获取配置信息
 * @param {*} id
 */
export function getWeiXinPermissionsValidationConfig(params) {
  return request({
    url: `/getWeiXinPermissionsValidationConfig`,
    method: 'get',
    // 如果参数是个对象,不加{}
    params: params
  })
}

openDefaultBrowser\storeTonken.vue

<template>
  <div />
</template>
<script>

export default {
  created() {
    // 获取URL中的token
    const token = window.location.href.split('token=')[1]
    this.$store.dispatch('user/singleSignIn', token).then(() => {
      // 跳转到首页
      this.$router.push({ path: '/' })
    })
  }
}
</script>

this.$store.dispatch(‘user/singleSignIn’, token) 是调用 Vuex 中的 actions,相关代码如下:

const actions = {
  // 省略其他代码
  // 单点登录设置token
  singleSignIn({ commit }, token) {
    return new Promise(resolve => {
      commit('SET_TOKEN', token)
      setToken(token)
      resolve()
    })
  }
}

后端代码

WeiXinController

package com.zhanyd.app.controller;

import com.alibaba.fastjson.JSONObject;
import com.zhanyd.app.common.ApiResult;
import com.zhanyd.app.common.util.HttpService;
import com.zhanyd.app.common.util.JwtUtils;
import com.zhanyd.app.common.util.StringHelp;
import com.zhanyd.app.common.weixin.WeixinHelper;
import com.zhanyd.app.service.UserLogService;
import com.zhanyd.app.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@EnableAutoConfiguration
public class WeiXinController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    UserService userInfoService;
    @Autowired
    UserLogService userLogService;

    /**
     * 企业微信域名校验
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/WW_ver*****TyAeb6.txt")
    public String wxPrivateKey(){
        return "Pm*****Aeb6";
    }


    /**
     * 单点登录,获取config接口注入权限验证配置
     * @param url
     * @return
     * @throws NoSuchAlgorithmException
     */
    @ResponseBody
    @GetMapping(value = "/getWeiXinPermissionsValidationConfig")
    public ApiResult<Map<String, Object>> getWeiXinPermissionsValidationConfig(String url) throws NoSuchAlgorithmException {
        logger.info("获取config信息");
        ApiResult<Map<String, Object>> apiResult = new ApiResult<Map<String, Object>>();
        Map<String, Object> resultMap = new HashMap<>(16);
        // 获取jsapi_ticket
        String ticket = WeixinHelper.getJsApiTicket(WeixinHelper.AFTER_SALE_PC_CORPSECRET, "");
        //当前时间戳转成秒
        long timestamp = System.currentTimeMillis() / 1000;
        //随机字符串
        String nonceStr = "Wm3W****cnW";
        // 获取JS-SDK使用权限签名
        String signature = WeixinHelper.getJSSDKSignature(ticket, nonceStr, timestamp, url);
        resultMap.put("corpid", WeixinHelper.APP_ID);
        resultMap.put("agentid", WeixinHelper.AFTER_SALE_PC_AGENT_ID);
        resultMap.put("timestamp", timestamp);
        resultMap.put("nonceStr", nonceStr);
        resultMap.put("signature", signature);
        logger.info("config resultMap = " + resultMap);
        return apiResult.success(resultMap);
    }


    /**
     * 企业微信登录
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/weixinLogin")
    public ApiResult<Map<String, Object>> weixinLogin(String code,HttpServletRequest request){

        ApiResult<Map<String, Object>> apiResult = new ApiResult<>();
        Map<String, Object> resultMap = new HashMap(16);

        logger.info("微信登录:");
        logger.info("code = " + code);

        //获取tonken
        String getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + WeixinHelper.APP_ID + "&corpsecret=" + WeixinHelper.AFTER_SALE_PC_CORPSECRET;
        String tokenContent = HttpService.post(getTokenUrl);
        logger.info("tokenContent = " + tokenContent);
        JSONObject jsonObject = JSONObject.parseObject(tokenContent);
        String accessToken = jsonObject.getString("access_token");
        logger.info("accessToken = " + accessToken);

        //获取用户信息
        String getUserUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=" + accessToken + "&code=" + code;
        String userContent = HttpService.post(getUserUrl);
        logger.info("userContent = " + userContent);
        jsonObject = JSONObject.parseObject(userContent);
        String userId = jsonObject.getString("UserId");
        logger.info("userId = " + userId);

        if(!StringHelp.isEmpty(userId)){
            
            // 登录逻辑,自行实现
            
            logger.info("{} 登录成功", StringHelp.valueOf(user.get("userName")));
            resultMap.put("userInfo", user);
            resultMap.put("token", token);
            logger.info("userInfo = " + user);
            logger.info("token = " + token);
            return apiResult.success(resultMap);

        }
        logger.info("{} 登录失败", userId);
    }

}

WeixinHelper

package com.zhanyd.app.common.weixin;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhanyd.app.common.util.HttpService;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class WeixinHelper {

	private static final Logger LOGGER = LoggerFactory.getLogger(WeixinHelper.class);

	// 企业id
	public static final String APP_ID = "****";
	// 售后平台应用
	public static final String AFTER_SALE_PC_AGENT_ID = "****";
	public static final String AFTER_SALE_PC_CORPSECRET = "****";

	/**
	 * 存放ticket的容器
	 */
	private static Map<String, Ticket> ticketMap = new HashMap<>();

	/**
	 * 获取token
	 * @return
	 */
	public static String getAccessToken(String secret) {
		//获取token
		String getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + WeixinHelper.APP_ID + "&corpsecret=" + secret;
		String tokenContent = HttpService.get(getTokenUrl);
		LOGGER.info("tokenContent = " + tokenContent);
		JSONObject jsonObject = JSONObject.parseObject(tokenContent);
		String accessToken = jsonObject.getString("access_token");
		LOGGER.info("accessToken = " + accessToken);
		return accessToken;
	}

	/**
	 * 获取jsapi_ticket
	 * @param secret
	 * @param type
	 * @return
	 */
	public static String getJsApiTicket(String secret, String type) {
		String accessToken = getAccessToken(secret);
		String key = accessToken;
		if (!StringUtils.isEmpty(accessToken)) {
			if ("agent_config".equals(type)){
				key = type + "_" + accessToken;
			}
			Ticket ticket = ticketMap.get(key);
			if (!ObjectUtils.isEmpty(ticket)) {
				long now = Calendar.getInstance().getTime().getTime();
				Long expiresIn = ticket.getExpiresIn();
				//有效期内的ticket 直接返回
				if (expiresIn - now > 0) {
					return ticket.getTicket();
				}
			}
			ticket = getJsApiTicketFromWeChatPlatform(accessToken, type);
			if (ticket != null) {
				ticketMap.put(key, ticket);
				return ticket.getTicket();
			}
		}
		return null;
	}


	/**
	 * 获取企业的jsapi_ticket或应用的jsapi_ticket
	 * @param accessToken
	 * @param type 为agent_config时获取应用的jsapi_ticket,否则获取企业的jsapi_ticket
	 * @return
	 */
	public static Ticket getJsApiTicketFromWeChatPlatform(String accessToken, String type) {
		String url;
		if ("agent_config".equals(type)) {
			url = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=" + accessToken+ "&type=" + type;
		} else {
			url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=" + accessToken;
		}
		Long now = System.currentTimeMillis();
		if (!StringUtils.isEmpty(accessToken)) {
			String body = HttpService.get(url);
			LOGGER.info("ticketContent = " + body);
			if (!StringUtils.isEmpty(body)) {
				JSONObject object = JSON.parseObject(body);
				if (object.getIntValue("errcode") == 0) {
					Ticket ticket = new Ticket();
					ticket.setTicket(object.getString("ticket"));
					ticket.setExpiresIn(now + object.getLongValue("expires_in") * 1000);
					return ticket;
				}
			}
		}
		return null;
	}


	/**
	 * 获取JS-SDK使用权限签名
	 * @param ticket
	 * @param nonceStr
	 * @param timestamp
	 * @param url
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static String getJSSDKSignature(String ticket, String nonceStr, long timestamp, String url) throws NoSuchAlgorithmException{
		String unEncryptStr = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;
		MessageDigest sha = MessageDigest.getInstance("SHA");
		// 调用digest方法,进行加密操作
		byte[] cipherBytes = sha.digest(unEncryptStr.getBytes());
		String encryptStr = Hex.encodeHexString(cipherBytes);
		return encryptStr;
	}

}

Ticket

package com.zhanyd.app.common.weixin;

/**
 * @author zhanyd
 * @date 2022-07-18
 */
public class Ticket {

    private String ticket;
    private Long expiresIn;

    public Ticket() {

    }

    public Ticket(String ticket, Long expiresIn) {
        this.ticket = ticket;
        this.expiresIn = expiresIn;
    }

    public String getTicket() {
        return ticket;
    }

    public void setTicket(String ticket) {
        this.ticket = ticket;
    }

    public Long getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(Long expiresIn) {
        this.expiresIn = expiresIn;
    }
}

HttpService

package com.zhanyd.app.common.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class HttpService {

	private static int readTimeout=25000;

	private static int connectTimeout=25000;

	private static final Logger LOGGER = LoggerFactory.getLogger(HttpService.class);
	/**
	 * POST方法
	 * @param sendUrl
	 * @param sendParam
	 * @return
	 */
	public static String post(String sendUrl, String sendParam) {

		StringBuffer receive = new StringBuffer();
		BufferedWriter dos = null;
		BufferedReader rd = null;
		HttpURLConnection URLConn = null;
		LOGGER.info("sendUrl = " + sendUrl + " sendParam = " + sendParam);
		
		try {

			URL url = new URL(sendUrl);
			URLConn = (HttpURLConnection) url.openConnection();
			URLConn.setReadTimeout(readTimeout);
			URLConn.setConnectTimeout(connectTimeout);
			URLConn.setDoOutput(true);
			URLConn.setDoInput(true);
			URLConn.setRequestMethod("POST");
			URLConn.setUseCaches(false);
			URLConn.setAllowUserInteraction(true);
			URLConn.setInstanceFollowRedirects(true);

			URLConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");

			if (sendParam!=null && sendParam.length()>0) {
				URLConn.setRequestProperty("Content-Length", String.valueOf(sendParam.getBytes().length));
				dos = new BufferedWriter(new OutputStreamWriter(URLConn.getOutputStream(), "UTF-8"));
				dos.write(sendParam);
				dos.flush();
			}

			rd = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));
			String line;
			while ((line = rd.readLine()) != null) {
				receive.append(line);
			}

		} catch (java.io.IOException e) {
			receive.append("访问产生了异常-->").append(e.getMessage());
			e.printStackTrace();
		} finally {
			if (dos != null) {
				try {
					dos.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
			
			if (rd != null) {
				try {
					rd.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
			
			URLConn.disconnect();
		}

		String content = receive.toString();
		LOGGER.info("content = "+content);
		return content;
	}

	public static String post(String sendUrl, String sendParam,String ContentType) {

		StringBuffer receive = new StringBuffer();
		BufferedWriter dos = null;
		BufferedReader rd = null;
		HttpURLConnection URLConn = null;
		LOGGER.info("sendUrl = " + sendUrl + " sendParam = " + sendParam);

		try {

			URL url = new URL(sendUrl);
			URLConn = (HttpURLConnection) url.openConnection();
			URLConn.setReadTimeout(readTimeout);
			URLConn.setConnectTimeout(connectTimeout);
			URLConn.setDoOutput(true);
			URLConn.setDoInput(true);
			URLConn.setRequestMethod("POST");
			URLConn.setUseCaches(false);
			URLConn.setAllowUserInteraction(true);
			URLConn.setInstanceFollowRedirects(true);

			URLConn.setRequestProperty("Content-Type", ContentType);

			if (sendParam!=null && sendParam.length()>0) {
				URLConn.setRequestProperty("Content-Length", String.valueOf(sendParam.getBytes().length));
				dos = new BufferedWriter(new OutputStreamWriter(URLConn.getOutputStream(), "UTF-8"));
				dos.write(sendParam);
				dos.flush();
			}

			rd = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));
			String line;
			while ((line = rd.readLine()) != null) {
				receive.append(line);
			}

		} catch (java.io.IOException e) {
			receive.append("访问产生了异常-->").append(e.getMessage());
			e.printStackTrace();
		} finally {
			if (dos != null) {
				try {
					dos.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}

			if (rd != null) {
				try {
					rd.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}

			URLConn.disconnect();
		}

		String content = receive.toString();
		LOGGER.info("content = "+content);
		return content;
	}

	public static String get(String sendUrl) {

		StringBuffer receive = new StringBuffer();
		HttpURLConnection URLConn = null;
		BufferedReader in = null;
		try {
			URL url = new URL(sendUrl);
			URLConn = (HttpURLConnection) url.openConnection();

			URLConn.setDoInput(true);
			URLConn.connect();

			in = new BufferedReader(new InputStreamReader(URLConn.getInputStream(), "UTF-8"));

			String line;
			while ((line = in.readLine()) != null) {
				receive.append(line);
			}

		} catch (IOException e) {
			receive.append("访问产生了异常-->").append(e.getMessage());
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (java.io.IOException ex) {
					ex.printStackTrace();
				}
				in = null;
			}
			
			URLConn.disconnect();

		}

		return receive.toString();
	}

	public static String post(String sendUrl) {
		return post(sendUrl, null);
	}
}

最后

最后我想说,我们在做项目的过程中肯定会碰到不少问题,碰到问题不要怕,遇到问题,我们就解决问题。

其实,带着问题去学习是一种很高效的学习方式,比如说Vuex,我以前也看过文档,但是一直看不太懂,始终一知半解,直到这次手动设置token Cookie的时候一直无法实现自动登录,原来项目中用了Vuex,要用Vuex的方式去设置。

借此机会,我又重新学了一遍Vuex文档,带着问题去学,答案都在文档里找到了,这样的学习效果就好很多了。

所以,遇到问题的时候,是我们学习的最佳时机,不要浪费任何一个问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

防火墙之双机热备篇

为什么要在防火墙上配置双机热备技术呢&#xff1f; 相信大家都知道&#xff0c;为了提高可靠性&#xff0c;避免单点故障 肯定有聪明的小伙伴会想到那为什么不直接多配置两台防火墙&#xff0c;然后再将他们进行线路冗余&#xff0c;不就完成备份了吗&#xff1f; 答案是不…

手机如何播放电脑的声音?

准备工具&#xff1a; 有线耳机&#xff0c;手机&#xff0c;电脑&#xff0c;远控软件 1.有线耳机插电脑上 2.电脑安装pc版远控软件&#xff0c;手机安装手机端控制版远控软件 3.手机控制电脑开启声音控制 用手机控制电脑后&#xff0c;打开声音控制&#xff0c;电脑播放视频…

【AI资讯】7.19日凌晨OpenAI发布迷你AI模型GPT-4o mini

性价比最高的小模型 北京时间7月19日凌晨&#xff0c;美国OpenAI公司推出一款新的 AI 模型“GPT-4o mini”&#xff0c;即GPT-4o的更小参数量、简化版本。OpenAI表示&#xff0c;GPT-4o mini是目前功能最强大、性价比最高的小参数模型&#xff0c;性能逼近原版GPT-4&#xff0…

谷粒商城实战笔记-37-前端基础-Vue-基本语法插件安装

文章目录 一&#xff0c;v-model1&#xff0c;双向绑定2&#xff0c;vue的双向绑定2.1 html元素上使用指令v-model2.2 model中声明对应属性2.3&#xff0c;验证view绑定modelmodel绑定view 完整代码 二&#xff0c;v-on1&#xff0c;指令简介2&#xff0c;在button按钮中添加v-…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(四)-无人机认证与授权

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…

【项目】星辰博客介绍

目录 一、项目背景 二、项目功能 1. 登录功能&#xff1a; 2. 列表页面&#xff1a; 3. 详情页面&#xff1a; 4. 写博客&#xff1a; 三、技术实现 四、功能页面展示 1. 用户登录 2. 博客列表页 3. 博客编辑更新页 4.博客发表页 5. 博客详情页 五.系统亮点 1.强…

C#发送内容到钉钉消息

文章目录 一、前提工作&#xff1a;配置钉钉机器人二、以text格式发送消息到钉钉三、以markdown格式发送消息到钉钉 一、前提工作&#xff1a;配置钉钉机器人 见链接&#xff1a;https://star-302.blog.csdn.net/article/details/135649084 下边的代码&#xff0c;钉钉安全设…

【C#】| 与 及其相关例子

按位或&#xff08;|&#xff09; 按位或运算符 | 对两个数的每一位进行比较&#xff0c;如果两个数中至少有一个为 1&#xff0c;则结果位为 1&#xff1b;否则&#xff0c;结果位为0。 1010 (10 in decimal) | 1100 (12 in decimal) ------1110 (14 in decimal) 力扣相关…

利用patch-package补丁,解决H5预览PDF时电子签章不显示问题

利用patch-package补丁&#xff0c;解决H5预览PDF时电子签章不显示问题 一、问题描述 在生产环境中&#xff0c;遇到了一个紧急的技术问题&#xff1a;用户在移动端H5页面上查看电子票时&#xff0c;PDF文件预览功能正常&#xff0c;但其中的电子签章未能正常显示。这一问题直…

el-select选择器修改背景颜色

<!--* FilePath: topSearch.vue* Author: 是十九呐* Date: 2024-07-18 09:46:03* LastEditTime: 2024-07-18 10:42:03 --> <template><div class"topSearch-container"><div class"search-item"><div class"item-name&quo…

笔记:Few-Shot Learning小样本分类问题 + 孪生网络 + 预训练与微调

内容摘自王老师的B站视频&#xff0c;大家还是尽量去看视频&#xff0c;老师讲的特别好&#xff0c;不到一小时的时间就缕清了小样本学习的基础知识点~Few-Shot Learning (1/3): 基本概念_哔哩哔哩_bilibili Few-Shot Learning&#xff08;小样本分类&#xff09; 假设现在每类…

UniVue@v1.5.0版本发布:里程碑版本

前言 以后使用UniVue都推荐使用1.5.0以后的版本&#xff0c;这个版本之后&#xff0c;更新的速度将会放缓。 希望这个框架能够切实的帮助大家更好的开发游戏&#xff0c;做出一款好游戏&#xff01;本开源项目采用的开源协议为MIT协议&#xff0c;完全开源化&#xff0c;以后也…

数据结构——线性表(循环链表)

一、循环链表定义 将单链表中终端结点的指针端由空指针改为指向头结点&#xff0c;就使整个单链表形成一 个环&#xff0c;这种头尾相接的单链表称为单循环链表&#xff0c;简称循环链表(circular linked list)。 循环链表解决了一个很麻烦的问题。如何从当中一 个结点出发&am…

二叉树的前、中、后序遍历(递归法、迭代法)leetcode144/94/145

leetcode144、二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a;…

【Linux】Linux环境设置环境变量操作步骤

Linux环境设置环境变量操作步骤 在一些开发过程中本地调试经常需要依赖环境变量的参数&#xff0c;但是怎么设置对小白来说有点困难&#xff0c;今天就介绍下具体的操作步骤&#xff0c;跟着实战去学习&#xff0c;更好的检验自己的技术水平&#xff0c;做技术还是那句话&…

三字棋游戏(C语言详细解释)

hello&#xff0c;小伙伴们大家好&#xff0c;算是失踪人口回归了哈&#xff0c;主要原因是期末考试完学校组织实训&#xff0c;做了俄罗斯方块&#xff0c;后续也会更新&#xff0c;不过今天先从简单的三字棋说起 话不多说&#xff0c;开始今天的内容 一、大体思路 我们都知…

pytest常用命令行参数解析

简介&#xff1a;pytest作为一个成熟的测试框架&#xff0c;它提供了许多命令行参数来控制测试的运行方式&#xff0c;以配合适用于不同的测试场景。例如 -x 可以用于希望出现错误就停止&#xff0c;以便定位和分析问题。–rerunsnum适用于希望进行失败重跑等个性化测试策略。 …

用ComfyUI安装可图Kolors大模型做手机壁纸

一、Kolors简介 国内科技公司快手在人工智能领域取得了显著进展&#xff0c;特别推出了「可图 Kolors」这一开源模型&#xff0c;它在图像生成质量上超越了SD3&#xff0c;与Midjourney v6模型相媲美&#xff0c;并支持中文提示词识别与生成中文字符&#xff0c;成为国产AI绘画…

经典神经网络(14)T5模型原理详解及其微调(文本摘要)

经典神经网络(14)T5模型原理详解及其微调(文本摘要) 2018 年&#xff0c;谷歌发布基于双向 Transformer 的大规模预训练语言模型 BERT&#xff0c;而后一系列基于 BERT 的研究工作如春笋般涌现&#xff0c;预训练模型也成为了业内解决 NLP 问题的标配。 2019年&#xff0c;谷歌…

Qt开发网络嗅探器03

数据包分析 想要知道如何解析IP数据包&#xff0c;就要知道不同的IP数据包的包头结构&#xff0c;于是我们上⽹查查资料&#xff1a; 以太网数据包 ARP数据包 IPv4 IPv6 TCP UDP ICMP ICMPv6 根据以上数据包头结构&#xff0c;我们就有了我们的protocol.h文件&#xff0c;声明…