前提条件:
1.注册微信小程序
2.获取appId和secret秘钥
3.小程序具备直播权限
小程序直播开发文档网址
目录
1.创建和修改直播间
2.删除直播间
3.获取直播间分享二维码
1.创建和修改直播间
两个功能一起写,区别在于,修改的时候需要多一个房间号属性。
1.根据开发文档创建直播间要发送以下JSON字段,所以创建一个实体类
修改直播间需要发送以下JSON字段,比上面就多了一个roomId
2.创建微信直播间实体类
import java.util.Date;
/**
* 调取微信接口创建直播间所用实体
*
* @author songrongzhen
* @date 2023/05/05
*/
public class WechatzbjView {
/**
* 直播间房号
*/
private Number id;
/**
* 直播间名字
* 是否必填:是
*/
private String name;
/**
* 背景图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
private String coverImg;
/**
* 直播计划开始时间(开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后)
* 是否必填:是
*/
private Number startTime;
/**
* 直播计划结束时间(开播时间和结束时间间隔不得短于30分钟,不得超过24小时)
* 是否必填:是
*/
private Number endTime;
/**
* 主播昵称
* 是否必填:是
*/
private String anchorName;
/**
* 主播微信号
* 是否必填:是
*/
private String anchorWechat;
/**
* 主播副号微信号
* 是否必填:否
*/
private String subAnchorWechat;
/**
* 创建者微信号
* 是否必填:否
*/
private String createrWechat;
/**
* 分享图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
private String shareImg;
/**
* 购物直播频道封面图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
private String feedsImg;
/**
* 是否开启官方收录 【1: 开启,0:关闭】,默认开启收录
* 是否必填:否
*/
private Number isFeedsPublic;
/**
* 直播间类型 【1: 推流,0:手机直播】
* 是否必填:是
*/
private Number type;
/**
* 是否关闭点赞 【0:开启,1:关闭】
* 是否必填:是
*/
private Number closeLike;
/**
* 是否关闭货架 【0:开启,1:关闭】
* 是否必填:是
*/
private Number closeGoods;
/**
* 是否关闭评论 【0:开启,1:关闭】
* 是否必填:是
*/
private Number closeComment;
/**
* 是否关闭回放 【0:开启,1:关闭】
* 是否必填:否
*/
private Number closeReplay;
/**
* 是否关闭分享 【0:开启,1:关闭】
* 是否必填:否
*/
private Number closeShare;
/**
* 是否关闭客服 【0:开启,1:关闭】
* 是否必填:否
*/
private Number closeKf;
我这里由于开发需求,需要在创建直播间的同时也需要把直播间的信息保存在自己的数据库中,所有我有创建了一个实体类,跟数据库字段对应,如需用到代码,自行生成set和get方法。
/**
* 微信直播间实体
*
* @author songrongzhen
* @date 2023/04/27
*/
@Entity
@Table(name = "HSGG_WECHATZBJ")
@DynamicInsert
@DynamicUpdate
public class Wechatzbj {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator") // 使用uuid的生成策略
private String id;
/**
* 直播间名字
* 是否必填:是
*/
@Column
@NotBlank(message = "直播间名字不能为空")
private String name;
/**
* 背景图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
@Column
@NotBlank(message = "请上传背景图")
private String coverImg;
/**
* 直播计划开始时间(开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后)
* 是否必填:是
*/
@Column
private Date startTime;
/**
* 直播计划结束时间(开播时间和结束时间间隔不得短于30分钟,不得超过24小时)
* 是否必填:是
*/
@Column
private Date endTime;
/**
* 主播昵称
* 是否必填:是
*/
@Column
@NotBlank(message = "主播昵称不能为空")
private String anchorName;
/**
* 主播微信号
* 是否必填:是
*/
@Column
@NotBlank(message = "主播微信号不能为空")
private String anchorWechat;
/**
* 主播副号微信号
* 是否必填:否
*/
@Column
private String subAnchorWechat;
/**
* 创建者微信号
* 是否必填:否
*/
@Column
private String createrWechat;
/**
* 分享图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
@Column
@NotBlank(message = "请上传分享图")
private String shareImg;
/**
* 购物直播频道封面图,填入mediaID(mediaID获取后,三天内有效)
* 是否必填:是
*/
@Column
@NotBlank(message = "请上传购物直播频道封面图")
private String feedsImg;
/**
* 是否开启官方收录 【1: 开启,0:关闭】,默认开启收录
* 是否必填:否
*/
@Column
private Long isFeedsPublic;
/**
* 直播间类型 【1: 推流,0:手机直播】
* 是否必填:是
*/
@Column
private Long type;
/**
* 是否关闭点赞 【0:开启,1:关闭】
* 是否必填:是
*/
@Column
private Long closeLike;
/**
* 是否关闭货架 【0:开启,1:关闭】
* 是否必填:是
*/
@Column
private Long closeGoods;
/**
* 是否关闭评论 【0:开启,1:关闭】
* 是否必填:是
*/
@Column
private Long closeComment;
/**
* 是否关闭回放 【0:开启,1:关闭】
* 是否必填:否
*/
@Column
private Long closeReplay;
/**
* 是否关闭分享 【0:开启,1:关闭】
* 是否必填:否
*/
@Column
private Long closeShare;
/**
* 是否关闭客服 【0:开启,1:关闭】
* 是否必填:否
*/
@Column
private Long closeKf;
/**
*主办方
*/
@Column
@NotBlank(message = "主办方不能为空")
private String zbf;
/**
* 机构id
*/
@Column
@NotBlank(message = "机构代码不能为空")
private String deptCode;
/**
* 区划代码
*/
@Column
@NotBlank(message = "区划代码不能为空")
private String regionCode;
/**
* 直播间状态
* 101 正常
*
* 102 未开始
*
* 103 已结束
*
* 104 禁播
*
* 105 暂停中
*
* 106 异常(微信未创建成功)
*
* 107 已过期:表示直播间一直未开播
*/
@Column
private String status;
/**
* 创建直播间成功后返回的房间id
*/
@Column
private String roomId;
/**
* 创建日期
*/
@Column
private Date createDate;
/**
* 修改时间
*/
@Column
private Date modifyDate;
/**
* 创建人
*/
@Column
private String createUser;
/**
* 修改人
*/
@Column
private String modifyUser;
/**
* 背景图media_id
*/
@Column
private String coverImgMedia;
/**
* 分享图media_id
*/
@Column
private String shareImgMedia;
/**
* 购物直播频道封面图media_id
*/
@Column
private String feedsImgMedia;
/**
* 分享二维码
*/
@Transient
private String cdnUrl;
/**
* 分享路径
*/
@Transient
private String pagePath;
/**
* 分享海报
*/
@Transient
private String posterUrl;
准备好这两个实体类后开始三层架构代码
2.controller层代码创建
/**
* 新建(修改)直播间
*
* @param wechatzbj
* @return {@link ResponseResult}<{@link ?}>
* @throws IOException ioexception
*/
@RequestMapping("/save")
@ResponseBody
public ResponseResult<?> save(Wechatzbj wechatzbj) {
return weChatzbManager.save(wechatzbj);
}
3.service层代码
根据是否有房间号来判断是新建直播间还是修改直播间
在调取微信所有接口前先获取一个调取凭证access_token,开发文档
/**
* 创建直播间
*
* @param wechatzbj wechatzbj
* @return {@link ResponseResult}<{@link ?}>
*/
public ResponseResult<?> save(Wechatzbj wechatzbj);
以上是接口
以下是实现
/**
* 创建/修改直播间
*
* @param wechatzbj wechatzbj
* @return {@link ResponseResult}<{@link ?}>
*/
@Override
public ResponseResult<?> save(Wechatzbj wechatzbj) {
//roomId为空创建直播间
if(StringUtils.isEmpty(wechatzbj.getRoomId())) {
try {
String url = GET_TOKEN_URL + String.format("wxaapi/broadcast/room/create?access_token=%s", WeChartRequestUtils.getToken());
getWechartZbj(wechatzbj,url);
} catch (IOException e) {
e.printStackTrace();
}
return ResponseResultUtils.genResult("创建直播间成功");
}
//修改直播间
else {
try {
String url = GET_TOKEN_URL + String.format("wxaapi/broadcast/room/editroom?access_token=%s", WeChartRequestUtils.getToken());
getWechartZbj(wechatzbj,url);
} catch (IOException e) {
e.printStackTrace();
}
return ResponseResultUtils.genResult("直播间修改成功");
}
}
public void getWechartZbj(Wechatzbj wechatzbj,String url){
WechatzbjView wechatzbjView = new WechatzbjView();
if (StringUtils.isNotEmpty(wechatzbj.getRoomId())){
wechatzbjView.setId( Long.parseLong(wechatzbj.getRoomId()));
}
wechatzbjView.setName(wechatzbj.getName());
wechatzbjView.setCoverImg(wechatzbj.getCoverImgMedia());
wechatzbjView.setStartTime(wechatzbj.getStartTime().getTime()/1000);
wechatzbjView.setEndTime(wechatzbj.getEndTime().getTime()/1000);
wechatzbjView.setAnchorName(wechatzbj.getAnchorName());
wechatzbjView.setAnchorWechat(wechatzbj.getAnchorWechat());
wechatzbjView.setSubAnchorWechat(wechatzbj.getSubAnchorWechat());
wechatzbjView.setCreaterWechat(wechatzbj.getCreaterWechat());
wechatzbjView.setShareImg(wechatzbj.getShareImgMedia());
wechatzbjView.setFeedsImg(wechatzbj.getFeedsImgMedia());
wechatzbjView.setIsFeedsPublic(wechatzbj.getIsFeedsPublic());
wechatzbjView.setType(wechatzbj.getType());
wechatzbjView.setCloseGoods(wechatzbj.getCloseGoods());
wechatzbjView.setCloseLike(wechatzbj.getCloseLike());
wechatzbjView.setCloseComment(wechatzbj.getCloseComment());
wechatzbjView.setCloseReplay(wechatzbj.getCloseReplay());
wechatzbjView.setCloseShare(wechatzbj.getCloseShare());
wechatzbjView.setCloseKf(wechatzbj.getCloseKf());
String param = JSONObject.toJSONString(wechatzbjView);
//创建直播间/修改
JSONObject jsonObject = OkHttpClientUtils.doPost(url, param);
//直播间创建(修改)失败提示
if (jsonObject.getInteger("errcode") != 0) {
//保存到数据库,直播间状态为异常
wechatzbj.setStatus("106");
weChatzbDao.save(wechatzbj);
System.err.println(jsonObject.get("errcode"));
throw new RuntimeException(jsonObject.getString("errmsg"));
}
if (wechatzbj != null) {
User user = UserUtils.getUser();
wechatzbj.setCreateUser(user.getName());
wechatzbj.setCreateDate(new Date());
wechatzbj.setModifyDate(new Date());
wechatzbj.setModifyUser(user.getName());
wechatzbj.setStatus("101");
if (StringUtils.isNotEmpty(jsonObject.getString("roomId"))) {
wechatzbj.setRoomId(jsonObject.getString("roomId"));
}
//直播间信息保存到自己数据库
weChatzbDao.save(wechatzbj);
}
}
我这里用的OkHttp发的请求,获取access_token工具类如下
public class WeChartRequestUtils {
static final String GET_TOKEN_URL = "https://api.weixin.qq.com/";
public static String getToken() throws IOException {
String param = "cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
String url = GET_TOKEN_URL + String.format(param, "", "");
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
if (response.body() != null) {
JSONObject jsonObject = JSONObject.parseObject(response.body().string());
if (Objects.nonNull(jsonObject.get("errcode"))) {
System.err.println(jsonObject.get("errcode"));
throw new RuntimeException(jsonObject.getString("errmsg"));
} else {
return jsonObject.getString("access_token");
}
}
} else {
throw new RuntimeException("调用失败");
}
return null;
}
}
service中还用了OKHttp去发送POST请求去调用微信接口创建直播间和修改直播间所以需要一个OKHttpUtils工具类。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class OkHttpClientUtils {
/**
* 日志对象
*/
private static Logger logger = LoggerFactory.getLogger(OkHttpClientUtils.class);
private static final Long CONNECT_TIMEOUT = 10000L;
private static final Long READ_TIMEOUT = 60000L;
private static final Long WRITE_TIMEOUT = 60000L;
public static String doGet(String url) {
return doGet(url, READ_TIMEOUT, WRITE_TIMEOUT);
}
public static String doPost(String url) {
return doPost(url, READ_TIMEOUT, WRITE_TIMEOUT);
}
/**
* 设置编码集
*/
private static final String ENCODE = "UTF-8";
private static OkHttpClient client = new OkHttpClient();
/**
* get请求
*
* @param url
* @return
* @throws IOException
*/
public static Response get(String url) throws IOException {
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(80000, TimeUnit.SECONDS)
.writeTimeout(80000, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url(url)
.build();
Response response = httpClient.newCall(request).execute();
return response;
}
public static String doGet(String url, Long readTimeout, Long waiteTimeout) {
String resultString = "";
Response response = null;
try {
//******** 一、 创建 httpClient 对象***********
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(waiteTimeout, TimeUnit.SECONDS)
.build();
// ******** 二、创建request 对象*************
Request request = new Request.Builder()
.url(url)
.get()
.build();
//********* 三、发送请求 ********************
response = httpClient.newCall(request).execute();
//********* 四、对响应进行处理 ***************
//1、如果 http 状态码不在 【200 ,300】区间内,抛出异常
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问
// 提供了诸如 string() 和 bytes() 这样的方法将流内的数据一次性读取完毕
resultString = response.body().string();
return resultString;
} catch (SocketTimeoutException e) {
// 超时
//logger.error("executeHttpRequest TimeOut, e: " + e);
System.out.println("executeHttpRequest TimeOut, e: " + e);
} catch (IOException e) {
// 网络IO异常
System.out.println("executeHttpRequest IOException, e: " + e);
} catch (Exception e) {
// 其他异常
e.printStackTrace();
} finally {
if (response != null) {
// body 必须被关闭,否则会发生资源泄漏;
response.body().close();
}
}
return resultString;
}
public static String doPost(String url, Long readTimeout, Long waiteTimeout) {
Response response = null;
String resultString = "";
try {
//******* 一、创建 httpClient 对象 **********
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(waiteTimeout, TimeUnit.SECONDS)
.build();
//******** 二、创建 request 对象 ************
Request request = new Request.Builder()
.url(url)
.build();
//******** 三、执行请求 ********************
response = httpClient.newCall(request).execute();
//******** 四、处理响应 ********************
if (!response.isSuccessful()) {
throw new IOException(" Unexpect code " + response);
}
resultString = response.body().string();
} catch (SocketTimeoutException e) {
// 超时
System.out.println("executeHttpRequest TimeOut, e: " + e);
} catch (IOException e) {
// 网络IO异常
System.out.println("executeHttpRequest IOException, e: " + e);
} catch (Exception e) {
// 其他异常
e.printStackTrace();
} finally {
if (response != null) {
// body 必须被关闭,否则会发生资源泄漏;
response.body().close();
}
}
return resultString;
}
public static JSONObject doPost(String url, String param) {
// 获取的返回数据
JSONObject result = new JSONObject();
// 生成HttpClient对象并设置参数
HttpClient httpClient = new HttpClient();
// 设置Http连接超时为20秒
HttpConnectionManagerParams params = httpClient.getHttpConnectionManager().getParams();
params.setConnectionTimeout(20000);
params.setSoTimeout(600000);
// 生成postMethod对象并设置参数
PostMethod postMethod = new PostMethod(url);
// 设置请求重试处理:请求二次
postMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(1, true));
postMethod.addRequestHeader("accept", "*/*");
postMethod.addRequestHeader("connection", "Keep-Alive");
// 设置json格式传送
postMethod.addRequestHeader("Content-Type", "application/json;charset=UTF-8");
// 封装请求数据
try {
RequestEntity requestEntity = new StringRequestEntity(param, "application/json", "UTF-8");
postMethod.setRequestEntity(requestEntity);
} catch (UnsupportedEncodingException e) {
result.put("resultCode", 0);
result.put("msg", "传输数据异常,请联系管理员!");
e.printStackTrace();
}
// 发送请求,获取返回结果
try {
int code = httpClient.executeMethod(postMethod);
if (code == HttpStatus.SC_OK) {
String res = postMethod.getResponseBodyAsString();
if (null != res && !"".equals(res)) {
result = JSON.parseObject(res);
result.put("resultCode", 1);
} else {
result.put("resultCode", -2);
result.put("msg", "数据返回异常,请联系管理员!");
}
}
} catch (IOException e) {
result.put("resultCode", -1);
result.put("msg", "请求异常,请联系管理员!");
e.printStackTrace();
} finally {
postMethod.releaseConnection();
}
return result;
}
/**
* GET请求
*
* @param url 请求的URL?拼参数
* @throws IOException
*/
public static String getToken(String url){
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
if (response.body() != null) {
JSONObject jsonObject = JSONObject.parseObject(response.body().string());
if (Objects.nonNull(jsonObject.get("errcode"))) {
System.err.println(jsonObject.get("errcode"));
throw new RuntimeException(jsonObject.getString("errmsg"));
} else {
return jsonObject.getString("access_token");
}
}
} else {
throw new RuntimeException("调用失败");
}
throw new RuntimeException("调用失败");
} catch (IOException e) {
e.printStackTrace();
}
throw new RuntimeException("调用失败");
}
}
这样就完成了创建直播间和修改直播间,
创建完成微信会返回房间号,需要保存到数据库
修改完直播间会返回errcode码
2.删除直播间
删除直播间调取接口,参数1access_token参数2房间号,发送get请求,即可删除
3.获取直播间分享二维码
这里重点说一下,避坑
调取接口后微信会返回两个图片链接一个是直播间分享图另一个是分享海报,前段不能直接用<img>标签src“图片链接”去展示,具体怎么不能展示,大家可自己试一下(卖个关子)。。。
所以就需要把返回的图片进行Base64转码,传给前段,然后前段去展示
1.controller层代码
/**
* 获取直播间分享二维码
*
* @param wechatzbj wechatzbj
* @param model 模型
* @return {@link String}
*/
@RequestMapping("getsharedcode")
public String getSharedCode(Wechatzbj wechatzbj, Model model){
model.addAttribute("wechart",weChatzbManager.getSharedCode(wechatzbj));
return "wechatzbj/sharedCodeView";
}
2.service层
/**
* 获取直播间分享二维码
*
* @param wechatzbj
* @return {@link String}
*/
public Wechatzbj getSharedCode(Wechatzbj wechatzbj);
以上是接口
以下是实现
/**
* 获取直播间分享二维码
*
* @param wechatzbj wechatzbj
* @return {@link String}
*/
@Override
public Wechatzbj getSharedCode(Wechatzbj wechatzbj) {
String url = null;
try {
if (StringUtils.isNotEmpty(wechatzbj.getRoomId())){
url = GET_TOKEN_URL + String.format("wxaapi/broadcast/room/getsharedcode?access_token=%s&roomId=%s", WeChartRequestUtils.getToken(),wechatzbj.getRoomId());
Response response = OkHttpClientUtils.get(url);
JSONObject jsonObject = JSONObject.parseObject(response.body().string());
System.out.println("获取直播间分享二维码"+jsonObject);
if (jsonObject.getInteger("errcode") != 0) {
System.err.println(jsonObject.get("errcode"));
throw new RuntimeException(jsonObject.getString("errmsg"));
}
wechatzbj.setCdnUrl(ImageUtils.toBase64(jsonObject.getString("cdnUrl")));
wechatzbj.setPagePath(jsonObject.getString("pagePath"));
wechatzbj.setPosterUrl(ImageUtils.toBase64(jsonObject.getString("posterUrl")));
return wechatzbj;
}
} catch (IOException e) {
e.printStackTrace();
}
return new Wechatzbj();
}
图片转Base64工具类(JDk8)
给一个图片链接返回该图片的Base64编码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;
public class ImageUtils {
public static String toBase64(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
try (InputStream is = url.openStream();
ByteArrayOutputStream os = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
return Base64.getEncoder().encodeToString(os.toByteArray());
}
}
}
当然你用的JDK是11的话可以这么写
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;
public class ImageUtils {
public static String toBase64(String imageUrl) throws IOException {
URL url = new URL(imageUrl);
InputStream is = url.openStream();
byte[] bytes = is.readAllBytes();
return Base64.getEncoder().encodeToString(bytes);
}
}
把转码后的编码传给前段,前段用以下方式去展示。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>展示Base64图片</title>
</head>
<body>
< img id="myImage" alt="图片">
<script>
// 假设我们从后端获取了一个名为base64Str的Base64字符串
const base64Str = "data:image/png;base64,iVBORw0KGg...";
// 获取img元素并设置src属性为Base64字符串
const img = document.getElementById("myImage");
img.src = base64Str;
</script>
</body>
</html>