import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
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.HttpClientBuilder;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 太阳码+水印处理
*
* @author
* @since 2024年03月11日
*/
@Slf4j
public class WxQrCodeImageUtil {
public static void main(String[] args) throws Exception {
String accessToken = getAccessToken("您的appid", "您的appsecret");
System.out.println("accessToken:" + accessToken);
WxScanCodeParam param = new WxScanCodeParam();
param.setAccessToken(accessToken);
param.setScene("a=b");
param.setPath("pages/index");
param.setWriteContent("测试:20240904");// 无需水印删除这一行
param.setFilePath("D:/tests");
param.setFileName(System.currentTimeMillis() + "");
generatUnlimitSunCode(param);
}
public static String getAccessToken(String appid, String appsecret)
{
String requestUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+appsecret+"";
String accessToken = null;
try
{
String response = HttpUtil.post(requestUrl,new HashMap<>());
JSONObject json = JSONObject.parseObject(response);
accessToken = String.valueOf(json.get("access_token"));
}
catch (Exception e)
{
log.error("getAccessToken error", e);
}
return accessToken;
}
/**
* 生成无限制的小程序码
* @param param
* @return
* @throws Exception
*/
public static String generatUnlimitSunCode(WxScanCodeParam param) throws Exception
{
String token =param.getAccessToken();
Map<String, String> params = new HashMap<>();
params.put("scene", param.getScene());
params.put("page", param.getPath());
//默认430
params.put("width", "430");
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token="+token);
httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
String body = JSON.toJSONString(params);
StringEntity entity = new StringEntity(body);
entity.setContentType("image/jpg");
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK)
{
HttpEntity entity2 = response.getEntity();
if(!entity2.getContentType().getValue().equals("image/jpeg"))
{
String result = EntityUtils.toString(entity2, "UTF-8");
log.error("generate sun code error,http execute result:" + result);
return null;
}
}
else
{
log.error("generate sun code error,http execute result:" + statusCode);
}
InputStream inputStream = response.getEntity().getContent();
//太阳码写标题
String content=param.getWriteContent();
if(StringUtils.isNotEmpty(content) && param.isWrite())
{
inputStream = addImageTitle(param.getWriteContent(), inputStream, 450, 450);
}
String name = param.getFileName()+".jpg";//文件名加后缀,跟上面对应
int flag = saveImg(inputStream, param.getFilePath(), name);// 保存图片
if (flag == 0)
{
throw new RuntimeException("保存图片[" + name + "]失败");
}
else
{
log.info("太阳码[{}]生成成功", name);
}
return param.getFilePath() + File.separatorChar + name;
}
public static int saveImg(InputStream inputStream, String filePath, String name) {
int result = 0;
FileOutputStream fos = null;
try {
byte[] buffer = new byte[1024];
int len;
fos = new FileOutputStream(new File(filePath, name));
while ((len = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
result = 1;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close(); // 关闭文件输出流
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 创建一个水印图片
*
* @param imageWidth 图片的宽度 px
* @param imageHeight 图片的高度 px
* @return
*/
public static BufferedImage createWatermarkImage(Integer imageWidth,
Integer imageHeight) {
BufferedImage bufferedImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setColor(Color.white);
graphics2D.fillRect(1,1,imageWidth-1,imageHeight-1);
graphics2D.setColor(Color.GRAY);
graphics2D.drawRect(0,0,imageWidth-1,imageHeight-1);
graphics2D.dispose();
return bufferedImage;
}
public static InputStream addImageTitle(String title, InputStream inputStream, int width, int height) {
try {
// 将InputStream转换为BufferedImage
BufferedImage bufferedImage = ImageIO.read(inputStream);
int addHeight = 60;
height = height + addHeight;
// 新建一个图层,大小为原图层的宽度和高度加上标题的高度
BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
BufferedImage watermarkImage = createWatermarkImage(width, addHeight);
// 在新图层上绘制原图层
Graphics2D g2d = newImage.createGraphics();
g2d.drawImage(bufferedImage, 0, 0, width, height-addHeight, null);
// 设置字体样式
int fontSize = 4;
String fontName = "宋体";
while (true){
Font font = new Font(fontName, Font.BOLD, fontSize);
int fontWight = g2d.getFontMetrics(font).stringWidth(title);
if (fontSize >= 30) {
fontSize = 30;
break;
}
if (fontWight + (3 * fontSize) < width) {
fontSize = fontSize + 2;
} else {
if (fontSize != 4) {
fontSize = fontSize - 2;
}
break;
}
}
Font font = new Font(fontName, Font.BOLD, fontSize);
g2d.setFont(font);
g2d.setColor(Color.BLACK);
int heightDiff = height - fontSize;
g2d.drawImage(watermarkImage, 0, height-addHeight, null);
// 水印透明设置
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1F));
int strWidth = getStrWidth(title, fontSize);
drawString(g2d, font, title, width, (width-strWidth)/2, heightDiff,0);
// 释放资源
g2d.dispose();
// 将新图层转换为InputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(newImage, "jpg", byteArrayOutputStream);
byte[] byteArray = byteArrayOutputStream.toByteArray();
return new ByteArrayInputStream(byteArray);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static boolean drawString(Graphics2D g, Font font, String text, int widthLength, int x, int y, int yn) {
FontMetrics fg = g.getFontMetrics(font);
List<String> ls = new ArrayList<>(2);
getListText(fg, text, widthLength, ls);
for (int i = 0; i < ls.size(); i++) {
if (i == 0) {
g.drawString(ls.get(i), x, y);
} else {
// 水平水印偏移
// g.drawString(ls.get(i), x, (y + yn));
// 倾斜水印 偏移
g.drawString(ls.get(i), x+yn, (y + yn));
}
}
if (ls.size() > 1) {
return true;
}
return false;
}
public static void getListText(FontMetrics fg, String text, int widthLength, List<String> ls) {
String ba = text;
boolean b = true;
int i = 1;
while (b) {
if (fg.stringWidth(text) > widthLength) {
text = text.substring(0, text.length() - 1);
i++;
} else {
b = false;
}
}
if (i != 1) {
ls.add(ba.substring(0, ba.length() - i));
getListText(fg, ba.substring(ba.length() - i), widthLength, ls);
} else {
ls.add(text);
}
}
/**
* 获取字符串占用的宽度
* <br>
* @param str 字符串
* @param fontSize 文字大小
* @return 字符串占用的宽度
*/
public static int getStrWidth(String str, int fontSize) {
char[] chars = str.toCharArray();
int fontSize2 = fontSize / 2;
int width = 0;
for (char c : chars) {
int len = String.valueOf(c).getBytes().length;
// 汉字为3,其余1
// 可能还有一些特殊字符占用2等等,统统计为汉字
if (len != 1) {
width += fontSize;
} else {
width += fontSize2;
}
}
return width;
}
}