针对优化的文章:
上篇文章缺点:
- 文段内容无法根据 “\n” 自动换行,只能实现每行占满后自动换行.
- 海报banner图片无法实现自动缩放,只能是一个长方形的banner图,局限性太大.
- 海报中的内容(图片位置,文段位置)都是以 背景图 的相对位置开始计算写入,造成不同手机可能出现内容位置不一致或者内容混乱的现象.
优化: 解决以上问题并实现
- 背景+banner+文段说明+二维码 = 海报
- 背景+诗词+诗词背景+文段说明+二维码 = 海报
如何挂级:
- 不管是小程序还是公众号(或其它),通过海报扫码的挂级,关键就在二维码
- 思路参考
- 约定好挂级的方式及二维码中的参数形式 (一般是 前端页面路径+当前用户标识)
- 将约定好的链接和参数形式当作二维码内容
- 生成二维码
- 其他用户扫码(进入前端页面路径-->前端开发人员通过代码获取二维码中的上级用户标识-->当前用户登录-->通过后台写好的挂级接口传给后台)
- 后端实现挂级逻辑操作
最后生成的海报如何处理:
- 通过graphics将海报绘画完成后
- 将海报写入服务器某个文件夹中
- 将海报上传至七牛云(或其它)
- 将海报访问链接存入数据库(或其它)
- 将服务器中的海报删除
不建议直接把服务器当作图片服务器,作为上传七牛云的中转站就好
graphics在服务器上可能存在的问题:
迭代:
- 上篇文章可以放弃了,就当参考吧.有需求的同学就看这篇文章就足够了!
效果图:
干货开始:
- 部分开发环境
- springboot+yml配置+Gradle (Gradle功能相当于maven,比maven更加简介,方便)
- github上的 WxJava 地址: https://github.com/Wechat-Group/WxJava/ 这个sdk我就不介绍了,熟悉微信开发的应该都熟悉
此篇不讲在项目中如何集成 WxJava- 七牛云(图片服务器) ⚠️如果是小程序中,那么七牛云的图片访问域名一定要配置成https
- 集成七牛云(在项目中如何使用七牛云上传图片)
⚠️七牛云账号申请等操作不详细讲解,都非常简单
- 登录七牛云 https://www.qiniu.com/ 注册自己的账号,不需要企业,个人就能用
- 好像需要个人认证
- 进入 控制台 --> 左边菜单栏 对象存储 --> 新建存储空间(一系列配置) --> 绑定域名
- 个人中心 --> 密匙管理 --> 拿到 AccessKey/SecretKey
⚠️这些地方不详细讲解,不同的再问或者百度
代码部分 - gralde
compile 'com.qiniu:qiniu-java-sdk:7.2.+'
- yml配置
qiniu: accessKey: vB4dBXKZB************************itjb-w8 secretKey: ser2PeRdL************************4GDvEfS bucket: xypsp domain: https://******.com/
- 配置文件
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** *属性文件 * @author rp * @date 2017/5/11 */ @ConfigurationProperties(prefix = "qiniu") @Data public class QiniuProperties { private String accessKey; private String secretKey; private String bucket; private String domain; }
import com.daoyintech.leshan.activity.utils.qiniu.QiniuUploadTool; import com.qiniu.common.Zone; import com.qiniu.storage.BucketManager; import com.qiniu.storage.Configuration; import com.qiniu.storage.UploadManager; import com.qiniu.util.Auth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; /** *七牛配置 * @author rp * @date 2017/5/11 */ @EnableConfigurationProperties(QiniuProperties.class) @org.springframework.context.annotation.Configuration public class QiniuConfiguration { @Autowired private QiniuProperties properties; @Bean @ConditionalOnMissingBean public UploadManager uploadManager(Configuration configuration){ UploadManager uploadManager = new UploadManager(configuration); return uploadManager; } @Bean public Configuration configuration(){ return new Configuration(Zone.zone2()); } @Bean @ConditionalOnMissingBean public Auth auth(){ Auth auth = Auth.create(properties.getAccessKey(), properties.getSecretKey()); return auth; } @Bean @ConditionalOnMissingBean public BucketManager bucketManager(Auth auth, Configuration configuration){ BucketManager bucketManager = new BucketManager(auth,configuration); return bucketManager; } @Bean @ConditionalOnMissingBean public QiniuUploadTool qiniuUpload(Auth auth, UploadManager uploadManager, BucketManager bucketManager){ QiniuUploadTool upload = new QiniuUploadTool(); upload.setAuth(auth); upload.setBucket(properties.getBucket()); upload.setUploadManager(uploadManager); upload.setBucketManager(bucketManager); return upload; }
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** *反射配置 * @author rp * @date 2017/4/25 */ @Component public class ApplicationContextProvider implements ApplicationContextAware { private static ApplicationContext CONTEXT; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextProvider.CONTEXT = applicationContext; } public static Object getBean(Class clazz) { return ApplicationContextProvider.CONTEXT.getBean(clazz); } public static Object getBean(String qualifier, Class clazz) { return ApplicationContextProvider.CONTEXT.getBean(qualifier , clazz); } }
- 七牛云工具类
import lombok.Data; /** * 结果返回属性 * @author rp */ @Data public class ImageResult { private boolean isSuccess; private String url; private String key; private String hash; }
import com.google.gson.Gson; import com.qiniu.common.QiniuException; import com.qiniu.http.Response; import com.qiniu.storage.BucketManager; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; /** * 上传tool * @author rp * @date 2017/5/9 */ @Data @Slf4j public class QiniuUploadTool { private Long expireTime = System.currentTimeMillis() / 1000; private String bucket; private UploadManager uploadManager; private BucketManager bucketManager; private String upToken; private Auth auth; @Autowired private QiniuProperties qiniuProperties; public DefaultPutRet upload(byte[] bytes, String key) { int date = 1000; if (System.currentTimeMillis() / date >= expireTime) { generateUpToken(); } Response response = null; DefaultPutRet putRet = null; try { response = uploadManager.put(bytes, key, upToken); putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); log.info("上传文件成功", putRet); } catch (QiniuException e) { log.error("图片上传失败:{}", e.getMessage()); } return putRet; } public DefaultPutRet delete(String key) throws QiniuException { Response response = bucketManager.delete(bucket, key); DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); return putRet; } private void generateUpToken() { this.expireTime = System.currentTimeMillis() / 1000 + 3600; this.upToken = auth.uploadToken(bucket); } }
import com.qiniu.common.QiniuException; import com.qiniu.storage.model.DefaultPutRet; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.UUID; /** * 上传tool封装 * @author rp */ @Component public class ImageScalaTool { private final QiniuUploadTool qiNiuUploadTool; private final QiniuProperties qiNiuProperties; public ImageScalaTool(QiniuUploadTool qiNiuUploadTool, QiniuProperties qiNiuProperties) { this.qiNiuUploadTool = qiNiuUploadTool; this.qiNiuProperties = qiNiuProperties; } public ImageResult upload(MultipartFile file, String key) { ImageResult imageResult; try { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); DefaultPutRet ret = qiNiuUploadTool.upload(file.getBytes(), key + "/" + uuid); imageResult = initializeQiNiuResult(true, ret); } catch (IOException e) { e.printStackTrace(); imageResult = initializeQiNiuResult(false, new DefaultPutRet()); } return imageResult; } public void delete(String key) { try { qiNiuUploadTool.delete(key); } catch (QiniuException e) { e.printStackTrace(); } } private ImageResult initializeQiNiuResult(boolean isSuccess, DefaultPutRet ret) { ImageResult imageResult = new ImageResult(); imageResult.setHash(ret.hash); imageResult.setKey(ret.key); imageResult.setSuccess(isSuccess); imageResult.setUrl(qiNiuProperties.getDomain() + "/" + ret.key); return imageResult; } }
- qiniuUploadTool 和 ImageScalaTool 都可直接上传图片,选其一即可
- ⚠️重点,主题来了-生成海报代码
⚠️将代码抽成了一个测试方法,同学可根据自己的实际情况运用到自己的项目中
gralde配置compile('commons-io:commons-io:2.5') implementation "com.github.binarywang:weixin-java-miniapp:3.3.0"
share: # 海报的构建背景图 bg: https://qiniu.xypsp.com/share_bg.jpg # 诗词背景图 peom_bg: https://qiniu.xypsp.com/61555056674_.pic_hd.jpg # 小程序的拍照列表页面路径 photo_path: /pages/activity/pages/activity-photo-indetail/main # 小程序的诗词列表页面路径 poem_path: /pages/activity/pages/activity-poem-indetail/main # 海报本地临时存储路径 local: /Users/rp/develop/image/
import cn.binarywang.wx.miniapp.api.WxMaService; import com.daoyintech.leshan.activity.configuration.qiniu.QiniuProperties; import com.daoyintech.leshan.activity.utils.qiniu.QiniuUploadTool; import com.qiniu.storage.model.DefaultPutRet; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import org.apache.commons.io.FileUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.net.URL; import java.util.UUID; @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class ActivityApplicationTests { @Value("${share.bg}") String shareBg; @Value("${share.peom_bg}") String peomBg; @Value("${share.photo_path}") String photoPath; @Value("${share.poem_path}") String poemPath; @Value("${share.local}") String local; @Autowired private WxMaService wxMaService; @Autowired private QiniuUploadTool qiniuUploadTool; @Autowired private QiniuProperties qiniuProperties; @Test public void contextLoads(){ //测试数据 String rule = "我正在参加美丽嘉州最美旅拍活动\n" + "快扫描右侧二维码每天帮我投一票吧\n" + "呼朋引伴来点赞,日周月赛不间断,奖金奖品疯狂赚!"; String content = "星星不见太阳光,\n" + "永眠长逝莫悲伤,\n" + "虚空极尽莫能计,\n" + "每在心旁总情长,\n" + "人随水去泪汪汪,\n" + "心力点点酒苍茫,\n" + "还记十月相倚伴,\n" + "谁人犹在我他旁。"; Long articleId = 1L; String banner = "https://static.leshan.daoyintech.com/article/url/1557625148918/a747f41b0d0149b9ab0c0ad46988b210"; String banner2 = "https://static.leshan.daoyintech.com/article/url/1557625346085/cb3ae7fb90be49169086bf320c8bf0f0"; int type = 0; //生成海报 String imageUrl = null; try { //主要方法 imageUrl = createShareImageUrl(rule,content,articleId,type,banner); } catch (Exception e) { log.error("生成二维码失败:{}",e.getMessage()); } } private String createShareImageUrl(String rule,String content,Long articleId,int type,String banner) throws Exception { //创建图片 BufferedImage img = new BufferedImage(750, 1334, BufferedImage.TYPE_INT_RGB); //开启画图 Graphics g = img.getGraphics(); //背景--读取互联网图片 BufferedImage back = ImageIO.read(new URL(shareBg)); // 绘制缩小后的图 g.drawImage(back.getScaledInstance(750, 1334, Image.SCALE_DEFAULT), 0, 0, null); if (type == 0){ //读取互联网图片--拍照大赛--动态计算写入海报的banner大小及位置 BufferedImage articleUrl = ImageIO.read(new URL(banner)); int urlWidth = articleUrl.getWidth(); int urlHeight = articleUrl.getHeight(); int width = 690; int height = 615; //从整体背景图的左边29位置开始 int x = 29; //从整体背景图顶部55位置开始 int y = 55; if (urlWidth >= urlHeight){ BigDecimal divide = new BigDecimal(width).divide(new BigDecimal(urlWidth), 2, BigDecimal.ROUND_HALF_UP); height = new BigDecimal(urlHeight).multiply(divide).intValue(); }else{ BigDecimal divide = new BigDecimal(height).divide(new BigDecimal(urlHeight), 2, BigDecimal.ROUND_HALF_UP); width = new BigDecimal(urlWidth).multiply(divide).intValue(); x = (750 - width - x - x) / 2 + 29; } g.drawImage(articleUrl.getScaledInstance(width,height,Image.SCALE_DEFAULT),x,y,null); }else{ //将诗词写入海报 createPeomContent(content, g, back); } //将规则描述写入海报 createDescriptionContent(rule, g, back); //创建二维码并写入海报 createQrCode(articleId, g); //保存到本地 String iconKey = articleId +"_"+ System.currentTimeMillis()+".png"; File file = new File(local + iconKey); ImageIO.write(img, "png",file); //上传到七牛云 DefaultPutRet upload = qiniuUploadTool.upload(FileUtils.readFileToByteArray(file), "articleId/share/qrCode/" + articleId + "/" + UUID.randomUUID().toString()); //删除本地文件 file.delete(); return qiniuProperties.getDomain()+upload.key; } private void createQrCode(Long articleId, Graphics g) throws IOException { //获取小程序二维码 String url = getProductQrCode(articleId,photoPath); //读取互联网图片 BufferedImage qrCode = ImageIO.read(new URL(url)); // 绘制缩小后的图 g.drawImage(qrCode.getScaledInstance(174, 174, Image.SCALE_DEFAULT), 535, 1057, null); //二维码字体 g.setFont(new Font("微软雅黑", Font.PLAIN, 25)); g.setColor(new Color(171,171,171)); //绘制文字 g.drawString("扫描或长按小程序码", 514, 1260); //关闭 g.dispose(); } private void createDescriptionContent(String rule, Graphics g, BufferedImage back) { //文案标题 // g.setFont(new Font("微软雅黑", Font.BOLD, 34)); // g.setColor(new Color(29,29,29)); //绘制文字 // g.drawString("说明", 31, 750); //文案内容 g.setFont(new Font("微软雅黑", Font.PLAIN, 30)); g.setColor(new Color(102,102,102)); int fontLen = getWatermarkLength(rule, g); //文字长度相对于图片宽度应该有多少行 int line = fontLen / (back.getWidth() - 90); //高度 int y = back.getHeight() - (line + 1) * 30 - 450; //文字叠加,自动换行叠加 int tempX = 32; int tempY = y; //单字符长度 int tempCharLen = 0; //单行字符总长度临时计算 int tempLineLen = 0; StringBuffer sb =new StringBuffer(); for(int i=0; i < rule.length(); i++) { char tempChar = rule.charAt(i); tempCharLen = getCharLen(tempChar, g); tempLineLen += tempCharLen; if(tempLineLen >= (back.getWidth()-90)) { //长度已经满一行,进行文字叠加 g.drawString(sb.toString(), tempX, tempY + 50); //清空内容,重新追加 sb.delete(0, sb.length()); tempY += 50; tempLineLen =0; } //追加字符 sb.append(tempChar); } //最后叠加余下的文字 g.drawString(sb.toString(), tempX, tempY + 50); } private void createPeomContent(String content, Graphics g, BufferedImage back){ //诗词大赛 g.setFont(new Font("微软雅黑", Font.PLAIN, 30)); g.setColor(new Color(64,64,64)); //只写入8行文字--内容太多以 ······ 结尾 int fontLineLen = 10; int fontLine = 0; //文字开始的高度 int y = 0; //文字叠加,自动换行叠加 int tempX = 50; int tempY = y; //单字符长度 int tempCharLen = 0; //单行字符总长度临时计算 int tempLineLen = 0; StringBuffer sb =new StringBuffer(); for(int i=0; i < content.length(); i++) { char tempChar = content.charAt(i); tempCharLen = getCharLen(tempChar, g); tempLineLen += tempCharLen; // 当每行占满自定义长度后或者出现 \n 换行 if(tempLineLen >= (back.getWidth()-110) || tempChar == '\n') { fontLine = fontLine + 1; if (fontLine > fontLineLen){ break; } //长度已经满一行,进行文字叠加 g.drawString(sb.toString(), tempX, tempY + 50); //清空内容,重新追加 sb.delete(0, sb.length()); tempY += 50; tempLineLen =0; } //追加字符 sb.append(tempChar); } //最后叠加余下的文字 if (fontLine > fontLineLen){ g.drawString("......", tempX, tempY + 50); }else { g.drawString(sb.toString(), tempX, tempY + 50); } } public String getProductQrCode(Long articleId, String photoPath) throws IOException { File qrCode = null; try { //海报中的小程序二维码必须png的透明形式 qrCode = wxMaService.getQrcodeService().createWxaCode(photoPath+"?id=" + articleId,430,true, null,true); } catch (WxErrorException e) { log.error("创建用户二维码错误:{}",e.getMessage()); } DefaultPutRet upload = qiniuUploadTool.upload(FileUtils.readFileToByteArray(qrCode), "articleId/qrCode/" + articleId + "/" + UUID.randomUUID().toString()); return qiniuProperties.getDomain()+upload.key; } /** * 获取水印文字总长度 *@paramwaterMarkContent水印的文字 *@paramg *@return水印文字总长度 */ public int getWatermarkLength(String waterMarkContent, Graphics g) { return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(),0, waterMarkContent.length()); } public int getCharLen(char c, Graphics g) { return g.getFontMetrics(g.getFont()).charWidth(c); } }
- ?
结语: 这篇博客是在晚上十一点开始写的,写了三分一后就没写了,然后第二天周末早上起来花了半天写完的! 希望对同学们有帮助,另外csdn的排版确实还是需要优化的,我已经尽量排的很好了,不要介意,写这边博客的时候才发 块引用 比 插入代码段 好用也好看,过会得去把我其他的博客排版都优化了才行!精力有限,暂时就不想在博客园等平台上写了!
建议: 同学们在学习的过程中还是要将代码理解透,不要只为达到目的而引用/套用,我的代码每一行注释已经非常详细了,不懂的地方还是仔细看一下吧,有利于根据自己的需求定制化开发!
最后一点小心思: 觉得对自己有用的同学们给我点个赞?吧,要是关注一下就更好了,博主写的不好的地方,请多见谅!