graphics生成分享挂级二维码海报之升级优化(完美方案)JAVA

2 篇文章 0 订阅
2 篇文章 0 订阅

针对优化的文章: 

上篇文章缺点:  

  1. 文段内容无法根据 “\n” 自动换行,只能实现每行占满后自动换行.
  2. 海报banner图片无法实现自动缩放,只能是一个长方形的banner图,局限性太大.
  3. 海报中的内容(图片位置,文段位置)都是以 背景图 的相对位置开始计算写入,造成不同手机可能出现内容位置不一致或者内容混乱的现象.

优化: 解决以上问题并实现

  1. 背景+banner+文段说明+二维码 = 海报
  2. 背景+诗词+诗词背景+文段说明+二维码 = 海报

如何挂级: 

  • 不管是小程序还是公众号(或其它),通过海报扫码的挂级,关键就在二维码
  • 思路参考
  1. 约定好挂级的方式及二维码中的参数形式 (一般是  前端页面路径+当前用户标识)
  2. 将约定好的链接和参数形式当作二维码内容
  3. 生成二维码
  4. 其他用户扫码(进入前端页面路径-->前端开发人员通过代码获取二维码中的上级用户标识-->当前用户登录-->通过后台写好的挂级接口传给后台)
  5. 后端实现挂级逻辑操作

最后生成的海报如何处理:

  1. 通过graphics将海报绘画完成后
  2. 将海报写入服务器某个文件夹中
  3. 将海报上传至七牛云(或其它)
  4. 将海报访问链接存入数据库(或其它)
  5. 将服务器中的海报删除
    不建议直接把服务器当作图片服务器,作为上传七牛云的中转站就好

graphics在服务器上可能存在的问题:

迭代:

  • 上篇文章可以放弃了,就当参考吧.有需求的同学就看这篇文章就足够了!

效果图:

  

干货开始:

  • 部分开发环境
  1. springboot+yml配置+Gradle  (Gradle功能相当于maven,比maven更加简介,方便)
  2. github上的 WxJava  地址: https://github.com/Wechat-Group/WxJava/   这个sdk我就不介绍了,熟悉微信开发的应该都熟悉
    此篇不讲在项目中如何集成 WxJava
  3. 七牛云(图片服务器) ⚠️如果是小程序中,那么七牛云的图片访问域名一定要配置成https
  • 集成七牛云(在项目中如何使用七牛云上传图片)
    ⚠️七牛云账号申请等操作不详细讲解,都非常简单
  1. 登录七牛云 https://www.qiniu.com/  注册自己的账号,不需要企业,个人就能用
  2. 好像需要个人认证
  3. 进入 控制台 --> 左边菜单栏  对象存储 --> 新建存储空间(一系列配置)  --> 绑定域名
  4. 个人中心 --> 密匙管理 --> 拿到 AccessKey/SecretKey
    ⚠️这些地方不详细讲解,不同的再问或者百度
    代码部分
  5. gralde
    compile 'com.qiniu:qiniu-java-sdk:7.2.+'
    
  6. yml配置
    qiniu:
      accessKey: vB4dBXKZB************************itjb-w8
      secretKey: ser2PeRdL************************4GDvEfS
      bucket: xypsp
      domain: https://******.com/
    ⚠️自己没有正式域名的,用七牛云提供的临时域名也可以,时效一个月
  7. 配置文件
    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);
        }
    } 
    
  8. 七牛云工具类
    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;
        }
    }
  9. qiniuUploadTool 和 ImageScalaTool 都可直接上传图片,选其一即可
  10. ⚠️重点,主题来了-生成海报代码
    ⚠️将代码抽成了一个测试方法,同学可根据自己的实际情况运用到自己的项目中
    gralde配置
    compile('commons-io:commons-io:2.5')
    implementation "com.github.binarywang:weixin-java-miniapp:3.3.0"
    yml配置
    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);
    }
    
    }
  11. ?

结语: 这篇博客是在晚上十一点开始写的,写了三分一后就没写了,然后第二天周末早上起来花了半天写完的! 希望对同学们有帮助,另外csdn的排版确实还是需要优化的,我已经尽量排的很好了,不要介意,写这边博客的时候才发 块引用 比 插入代码段 好用也好看,过会得去把我其他的博客排版都优化了才行!精力有限,暂时就不想在博客园等平台上写了!

建议: 同学们在学习的过程中还是要将代码理解透,不要只为达到目的而引用/套用,我的代码每一行注释已经非常详细了,不懂的地方还是仔细看一下吧,有利于根据自己的需求定制化开发!

最后一点小心思: 觉得对自己有用的同学们给我点个赞?吧,要是关注一下就更好了,博主写的不好的地方,请多见谅!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值