为您推荐:
精华内容
最热下载
问答
  • 5星
    35.15MB HowardEmily 2021-01-14 11:50:13
  • 5星
    33.7MB qq_41934573 2021-05-04 12:48:01
  • 5星
    85.18MB qq_17556735 2021-03-13 23:36:30
  • 5星
    3.17MB qq_45934521 2021-07-12 08:37:52
  • 5星
    90KB XingLongSKY 2021-02-22 10:32:37
  • 5星
    241B qq_40957277 2021-08-16 19:56:19
  • 5星
    38.56MB dhyuan_88 2021-04-21 15:01:02
  • 5星
    1.25MB rongbo91 2021-01-12 14:11:51
  • 5星
    11.95MB bala5569 2020-12-30 01:21:56
  • 5星
    120KB h5course 2021-03-25 23:44:24
  • 7KB weixin_42119281 2021-02-05 17:56:29
  • 4星
    21.89MB chgyog 2013-09-18 11:27:19
  • 在用户购买会员后的第二天下午四点发送微信模板消息 在pom.xml里面配置 <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator&...

    在用户购买会员卡后的第二天下午四点发送微信模板消息

    在pom.xml里面配置

    <dependency>
      <groupId>net.coobird</groupId>
      <artifactId>thumbnailator</artifactId>
      <version>0.4.8</version>
    </dependency>
    
    <dependency>
      <groupId>com.github.binarywang</groupId>
      <artifactId>weixin-java-miniapp</artifactId>
      <version>${wexin-tools-version}</version>
    </dependency>
    <dependency>
      <groupId>com.github.binarywang</groupId>
      <artifactId>weixin-java-mp</artifactId>
      <version>3.1.0</version>
    </dependency>
    
    <dependency>
      <groupId>com.github.binarywang</groupId>
      <artifactId>weixin-java-common</artifactId>
      <version>${wexin-tools-version}</version>
    </dependency>
    <dependency>
      <groupId>com.github.binarywang</groupId>
      <artifactId>weixin-java-pay</artifactId>
      <version>${wexin-tools-version}</version>
    </dependency>
    

    所以先在数据库里面建了一张表,把发送时间写进去,把微信模板中要用到的信息存入到数据库中,并设置一个标识去标识这个用户有没有被发送过消息。然后写个定时任务,每隔一定时间去把这个时间拿出来跟当前时间作比较,当前时间大于存在数据库里的时间就执行发送模板消息,然后把标识改成1。

    这里面的OPENID都是公众号的openid,不是小程序的openid.可以根据小程序的unionID去找到用户在公众号中的openid

    package com.umshuo.service;
    
    import com.aliyun.openservices.shade.com.alibaba.fastjson.JSON;
    import com.aliyun.openservices.shade.com.alibaba.fastjson.JSONObject;
    import com.qiniu.util.StringUtils;
    import com.umshuo.domain.Business;
    import com.umshuo.domain.Receiver;
    import com.umshuo.domain.User;
    import lombok.extern.slf4j.Slf4j;
    import me.chanjar.weixin.common.error.WxErrorException;
    import me.chanjar.weixin.mp.api.WxMpService;
    import me.chanjar.weixin.mp.bean.result.WxMpUser;
    import me.chanjar.weixin.mp.bean.result.WxMpUserList;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    @Slf4j
    @Service
    public class SyncOpenIdService {
        @Autowired
        @Qualifier("wxMpService")
        private WxMpService wxMpService;
        @Autowired
        @Qualifier("wxMpServiceBusiness")
        private WxMpService wxMpServiceBusiness;
        @Autowired
        private UserService userService;
        @Autowired
        private ReceiverService receiverService;
        @Autowired
        private BusinessService businessService;
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        public void syncFromWechat(String lastOpenId){
            try {
    
                log.info("同步关注微信公众号社区买手的用户信息 - lastMpOpenId: [{}]", lastOpenId);
                WxMpUserList list = getWxMpUserList(lastOpenId);
                log.info("获取的用户数量: " + list.getOpenids().size());
                List<Map<String,Object>> userList = userService.selectUnionList();
                int count=0;
                // 判断是否有新关注用户
                if (list != null && list.getOpenids().size() > 0  && userList!=null && userList.size()>0) {
                    for (String openid : list.getOpenids()) {
                        WxMpUser wxUser = wxMpService.getUserService().userInfo(openid);
    //                    log.debug("获取用户信息: openId:[{}], wxUser:[{}]", openid, JSON.toJSONString(wxUser));
                        boolean result = updateUser(userList,wxUser.getUnionId(),wxUser.getOpenId());
                        if(result){
                            count++;
                            log.info("更新的用户信息- openId:{},UnionId:{}", wxUser.getOpenId(),wxUser.getUnionId());
                        }
    //                    if (result != true) {
    //                        // 更新用户公众号openId失败, 将用户信息暂存redis
    //                        //log.info("用户信息不存在,临时存储到redis - openId:[{}]", openid);
    //                        //redisTemplate.opsForValue().set("wx_syncuser_" + wxUser.getUnionId(), JSON.toJSONString(wxUser));
    //                    }
                    }
                    //redisTemplate.opsForValue().set("lastMpOpenId", list.getOpenids().get(list.getOpenids().size() - 1));
                }
                log.info("----更新关注社区买手微信公众号openid的用户数:"+count);
            } catch (Exception ex) {
                log.error(ex.getMessage());
                ex.printStackTrace();
            }
        }
    
        /**
         * 同步旗手和商户的微信openID
         * @param lastOpenId
         */
        public void syncBusinessFromWechat(String lastOpenId){
            try {
    
                log.info("同步关注社区达微信公众号的用户信息 - lastMpOpenId: [{}]", lastOpenId);
                WxMpUserList list = getWxMpBusinessUserList(lastOpenId);
                log.info("获取的用户数量: " + list.getOpenids().size());
                List<Map<String,Object>> busiList = businessService.selectUnionList();
                List<Map<String,Object>> recieveList = receiverService.selectUnionList();
                int count=0;
                // 判断是否有新关注用户
                if (list != null && list.getOpenids().size() > 0  && ((busiList!=null && busiList.size()>0)||(recieveList!=null && recieveList.size()>0))) {
                    boolean result = false;
                    for (String openid : list.getOpenids()) {
                        WxMpUser wxUser = wxMpServiceBusiness.getUserService().userInfo(openid);
                        result = updateBusiUser(busiList,wxUser.getUnionId(),wxUser.getOpenId());
                        if(result){
                            count++;
                            log.info("更新商户openID信息- openId:{},UnionId:{}", wxUser.getOpenId(),wxUser.getUnionId());
                        }
                        result = updateRecieveUser(recieveList,wxUser.getUnionId(),wxUser.getOpenId());
                        if(result){
                            count++;
                            log.info("更新骑手openID信息- openId:{},UnionId:{}", wxUser.getOpenId(),wxUser.getUnionId());
                        }
                    }
                }
                log.info("----更新社区达微信公众号openid的用户数:"+count);
            } catch (Exception ex) {
                log.error(ex.getMessage());
                ex.printStackTrace();
            }
        }
    
        /**
         * 更新用户微信公众号openId
         * @return
         */
        public boolean updateUser(List<Map<String,Object>> userList,String unionId, String  openId) {
            // 查询到用户信息, 更新用户公众号openId
            if (openId!=null && unionId!=null) {
                for (Map<String,Object> map:userList) {
                    if(map.get("unionId").equals(unionId)){
                        if(map.get("wxOpenId")==null || !openId.equals(map.get("wxOpenId"))){
                            User user = new User();
                            user.setId((Long)map.get("id"));
                            user.setWxOpenId(openId);
                            int reu = userService.updateUser(user);
                            if(reu>0) return true;
                        }
                        break;
                    }
                }
            }
            return false;
        }
    
        /**
         * 更新商户微信公众号openId
         * @return
         */
        public boolean updateBusiUser(List<Map<String,Object>> busiList,String unionId, String  openId){
            // 查询到商户信息表, 更新用户公众号openId
            if (openId!=null && unionId!=null) {
                for (Map<String,Object> map:busiList) {
                    if(map.get("unionId").equals(unionId)){
                        if(map.get("wxOpenId")==null || !openId.equals(map.get("wxOpenId"))){
                            Business business = new Business();
                            business.setId((Long)map.get("id"));
                            business.setWxOpenId(openId);
                            int reu = businessService.updateBusiness(business);
                            if(reu>0) return true;
                        }
                        break;
                    }
                }
            }
            return false;
        }
    
        /**
         * 更新商户微信公众号openId
         * @return
         */
        public boolean updateRecieveUser(List<Map<String,Object>> reviList,String unionId, String  openId){
            //更新骑手表公众号openId
            if (openId!=null && unionId!=null) {
                for (Map<String,Object> map:reviList) {
                    if(map.get("unionId").equals(unionId)){
                        if(map.get("wxOpenId")==null || !openId.equals(map.get("wxOpenId"))){
                            Receiver receiver = new Receiver();
                            receiver.setId((Long)map.get("id"));
                            receiver.setWxOpenId(openId);
                            int reu = receiverService.updateReceiver(receiver);
                            if(reu>0) return true;
                        }
                        break;
                    }
                }
            }
            return false;
        }
    
    
        /**
         * 获取微信公众号关注用户列表
         *
         * @param lastOpenId
         * @return
         * @throws WxErrorException
         */
        private WxMpUserList getWxMpUserList(String lastOpenId) throws WxErrorException {
            return wxMpService.getUserService().userList(lastOpenId);
        }
    
        /**
         * 获取微信公众号关注用户列表
         *
         * @param lastOpenId
         * @return
         * @throws WxErrorException
         */
        private WxMpUserList getWxMpBusinessUserList(String lastOpenId) throws WxErrorException {
            return wxMpServiceBusiness.getUserService().userList(lastOpenId);
        }
    }
    

     

    定时任务:

     

     

    第一个:

     

    2.

    WechatMpConfig 

    package com.umshuo.properties;
    
    import me.chanjar.weixin.mp.api.WxMpConfigStorage;
    import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
    import me.chanjar.weixin.mp.api.WxMpService;
    import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    @Component
    public class WechatMpConfig {
        @Autowired
        WechatAccountConfig wechatAccountConfig;
    
        /**
         * 微信服务装载
         * @return
         */
        @Bean
        public WxMpService wxMpService() {
            WxMpService wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
            return wxMpService;
        }
    
        /**
         * 微信服务装载
         * @return
         */
        @Bean
        public WxMpService wxMpServiceBusiness() {
            WxMpService wxMpService = new WxMpServiceImpl();
            wxMpService.setWxMpConfigStorage(wxMpConfigStorageBusiness());
            return wxMpService;
        }
    
        /**
         * 配置温馨的APPID和密码
         * @return
         */
        @Bean
        public WxMpConfigStorage wxMpConfigStorage() {
            WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
            wxMpConfigStorage.setAppId(wechatAccountConfig.getAppId());
            wxMpConfigStorage.setSecret(wechatAccountConfig.getSecret());
            return wxMpConfigStorage;
        }
    
        /**
         * 配置温馨的APPID和密码
         * @return
         */
        @Bean
        public WxMpConfigStorage wxMpConfigStorageBusiness() {
            WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
            wxMpConfigStorage.setAppId(wechatAccountConfig.getAppIdBusiness());
            wxMpConfigStorage.setSecret(wechatAccountConfig.getSecretBusiness());
            return wxMpConfigStorage;
        }
    
    }

    3.

     WxMpProperties

    package com.umshuo.properties;
    
    import org.apache.commons.lang3.builder.ToStringBuilder;
    import org.apache.commons.lang3.builder.ToStringStyle;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @ConfigurationProperties(prefix = "wechat.mp")
    public class WxMpProperties {
    
       /**
        * 设置微信公众号的appid
        */
       private String appId;
    
       /**
        * 设置微信公众号的app secret
        */
       private String secret;
    
       /**
        * 设置微信公众号的token
        */
       private String token;
    
       /**
        * 设置微信公众号的EncodingAESKey
        */
       private String aesKey;
    
       public String getAppId() {
          return this.appId;
       }
    
       public void setAppId(String appId) {
          this.appId = appId;
       }
    
       public String getSecret() {
          return this.secret;
       }
    
       public void setSecret(String secret) {
          this.secret = secret;
       }
    
       public String getToken() {
          return this.token;
       }
    
       public void setToken(String token) {
          this.token = token;
       }
    
       public String getAesKey() {
          return this.aesKey;
       }
    
       public void setAesKey(String aesKey) {
          this.aesKey = aesKey;
       }
    
       @Override
       public String toString() {
          return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
       }
    }
    

     

     wxservice:

    package com.umshuo.service;
    
    import com.alibaba.fastjson.JSON;
    import com.google.common.cache.Cache;
    import com.google.common.cache.CacheBuilder;
    import com.google.common.collect.Lists;
    import com.umshuo.common.JsonUtil;
    import com.umshuo.common.ServerResponse;
    import com.umshuo.common.StringUtil;
    import com.umshuo.common.WXBizDataCrypt;
    import com.umshuo.properties.WechatAccountConfig;
    import com.umshuo.properties.WechatMpConfig;
    import com.umshuo.vo.SessionKeyVo;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    import me.chanjar.weixin.common.error.WxErrorException;
    import me.chanjar.weixin.mp.api.WxMpService;
    import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
    import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
    import org.json.JSONException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import javax.crypto.BadPaddingException;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import java.io.IOException;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author chezhu.xin
     */
    @Slf4j
    @Service
    public class WxService {
        @Autowired
        @Qualifier("wxMpService")
        private WxMpService mpService;
        @Autowired
        @Qualifier("wxMpServiceBusiness")
        private WxMpService wxMpServiceBusiness;
        @Autowired
        private WechatAccountConfig wechatAccountConfig;
    
        private static final Cache<String, String> CACHE = CacheBuilder.newBuilder()
                .initialCapacity(1000)
                .maximumSize(20000)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build();
    
        private static final Cache<String, String> OPENID_CACHE = CacheBuilder.newBuilder()
                .initialCapacity(1000)
                .maximumSize(20000)
                .expireAfterWrite(30, TimeUnit.MINUTES)
                .build();
    
        @Getter
        private enum WxAppIdEnum {
            /**
             * 微信小程序信息
             */
            APP_1("小程序名字", "这里放APPID", "这里放模板id"),
            APP_2("小程序名字", "这里放APPID", "这里放模板id"),
            APP_3("小程序名字", "这里放APPID", "这里放模板id");
    
            private final String desc;
            private final String appId;
            private final String appSecret;
    
    
            WxAppIdEnum(String desc, String appId, String appSecret) {
                this.desc = desc;
                this.appId = appId;
                this.appSecret = appSecret;
            }
    
    
            public static WxAppIdEnum getWxAppIdEnum(String appId) {
                for (WxAppIdEnum wxAppIdEnum : Lists.newArrayList(WxAppIdEnum.values())) {
                    if (Objects.equals(appId, wxAppIdEnum.getAppId())) {
                        return wxAppIdEnum;
                    }
                }
                return null;
            }
        }
    
        public ServerResponse<SessionKeyVo> sessionKey(String code, String appId) throws JSONException {
            WxAppIdEnum wxAppIdEnum = WxAppIdEnum.getWxAppIdEnum(appId);
            if (wxAppIdEnum == null) {
                return ServerResponse.createByErrorMessage(appId);
            }
            String requestAppId = wxAppIdEnum.appId;
            String requestAppSecret = wxAppIdEnum.appSecret;
    
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("https://api.weixin.qq.com/sns/jscode2session");
            stringBuilder.append("?").append("appid").append("=").append(requestAppId);
            stringBuilder.append("&").append("secret").append("=").append(requestAppSecret);
            stringBuilder.append("&").append("js_code").append("=").append(code);
            stringBuilder.append("&").append("grant_type").append("=").append("authorization_code");
            String result = new RestTemplate().getForObject(stringBuilder.toString(), String.class);
            log.debug(stringBuilder.toString());
            log.debug(result);
    
            String sessinKey = JsonUtil.toJsonObject(result).getString("session_key");
            String openid = JsonUtil.toJsonObject(result).getString("openid");
            log.debug("添加缓存=="+openid);
            CACHE.put(code, sessinKey);
            OPENID_CACHE.put(code, openid);
            SessionKeyVo sessionKeyVo = JsonUtil.fromJson(result, SessionKeyVo.class);
            return ServerResponse.createBySuccess(sessionKeyVo);
        }
    
        public ServerResponse<SessionKeyVo> descrypt(String code, String appId, String encryptedData, String iv) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, JSONException {
            if (StringUtil.isBlank(code)) {
                return ServerResponse.createByErrorMessage("code 值不允许为空");
            }
            String sessionKey = CACHE.getIfPresent(code);
            if (StringUtil.isBlank(sessionKey)) {
                WxAppIdEnum wxAppIdEnum = WxAppIdEnum.getWxAppIdEnum(appId);
                if (wxAppIdEnum == null) {
                    return ServerResponse.createByErrorMessage(appId);
                }
                String requestAppId = wxAppIdEnum.appId;
                String requestAppSecret = wxAppIdEnum.appSecret;
                String stringBuilder = "https://api.weixin.qq.com/sns/jscode2session" +
                        "?" + "appid" + "=" + requestAppId +
                        "&" + "secret" + "=" + requestAppSecret +
                        "&" + "js_code" + "=" + code +
                        "&" + "grant_type" + "=" + "authorization_code";
                String resultStr = new RestTemplate().getForObject(stringBuilder, String.class);
                String str = JsonUtil.toJsonObject(resultStr).getString("session_key");
                String openid = JsonUtil.toJsonObject(resultStr).getString("openid");
                if (StringUtil.isNotBlank(str)) {
                    sessionKey = str;
                    CACHE.put(appId,sessionKey);
                }
                log.debug("查询的结果:{}",resultStr);
                if(StringUtil.isNotBlank(resultStr)){
                  SessionKeyVo sessionKeyVo = JsonUtil.fromJson(resultStr, SessionKeyVo.class);
                  return ServerResponse.createBySuccess(sessionKeyVo);
                }
            }
            String result = WXBizDataCrypt.getInstance().decrypt(encryptedData, sessionKey, iv, "utf-8");
            log.debug("result:{}", result);
            SessionKeyVo sessionKeyVo = JsonUtil.fromJson(result, SessionKeyVo.class);
            return ServerResponse.createBySuccess(sessionKeyVo);
        }
    
        /**
         * 解密unionID
         */
        public String descrypt(String sessionKey, String encryptedData, String iv) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
            String result = WXBizDataCrypt.getInstance().decrypt(encryptedData, sessionKey, iv, "utf-8");
            log.debug("result:{}", result);
            SessionKeyVo sessionKeyVo = JsonUtil.fromJson(result, SessionKeyVo.class);
            return sessionKeyVo.getUnionid();
        }
    
        /**
         * 发送社区买手模板消息
         * @param templateMap
         * @return
         */
        public boolean sendMessage(Map<String,Object> templateMap) {
            try {
                WxMpTemplateMessage template = new WxMpTemplateMessage();
                template.setToUser((String)templateMap.get("openId"));
                template.setTemplateId((String)templateMap.get("templateId"));
                template.setMiniProgram(new WxMpTemplateMessage.MiniProgram(wechatAccountConfig.getMiniAppId(),(String)templateMap.get("url")));
                template.setData((List<WxMpTemplateData>)templateMap.get("data"));
                String response = mpService.getTemplateMsgService().sendTemplateMsg(template);
                log.debug("发送微信模板信息结果 - openId:[{}], response:[{}]", template.getToUser(), JSON.toJSONString(response));
                return true;
    
            } catch (WxErrorException ex) {
                ex.printStackTrace();
                log.error("【微信模版消息】发送失败, {}", ex);
            }
            return false;
        }
        /**
         *
         */
        /**
         * 发送社区达模板消息
         * @param templateMap
         * @return
         */
        public boolean sendMessageBusiness(Map<String,Object> templateMap) {
            try {
                WxMpTemplateMessage template = new WxMpTemplateMessage();
                template.setToUser((String)templateMap.get("openId"));
                template.setTemplateId((String)templateMap.get("templateId"));
                template.setMiniProgram(new WxMpTemplateMessage.MiniProgram(wechatAccountConfig.getMiniAppIdBusiness(),(String)templateMap.get("url")));
                template.setData((List<WxMpTemplateData>)templateMap.get("data"));
                String response = wxMpServiceBusiness.getTemplateMsgService().sendTemplateMsg(template);
                log.debug("发送微信模板信息结果 - openId:[{}], response:[{}]", template.getToUser(), JSON.toJSONString(response));
                return true;
    
            } catch (WxErrorException ex) {
                ex.printStackTrace();
                log.error("【微信模版消息】发送失败, {}", ex);
            }
            return false;
        }
    
    }
    

     

     

    展开全文
    loveliness_peri 2018-11-01 16:52:10
  • 94KB weixin_38267845 2018-06-20 21:32:30
  • 3星
    27.97MB chgyog 2013-09-18 11:08:23
  • Java模板引擎 FreeMarker 概念 介绍 特性 优势 不足 一个小Demo 概念 FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写。它是为Java程序员提供的一个开发包。它不是面向最终...

    Java模板引擎 FreeMarker

    概念
    介绍
    特性
    优势
    不足
    一个小Demo

    概念

    FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写。它是为Java程序员提供的一个开发包。它不是面向最终用户的,而是为程序员提供的一款可以嵌入他们所开发产品的应用程序。

    介绍

    那么,FreeMarker是一款怎样的工具呢?FreeMarker实际上是被设计用来生成HTML Web页面,尤其是通过实现了基于MVC模式的Java Servlet应用程序。使用MVC模式的动态页面的设计构思使得你可以将前端设计师从程序员中分离出来。所有人各司其职,发挥其最擅长的一面。

    网页设计师可以改写页面的显示效果而不受程序员编译代码的影响,因为应该程序的逻辑和页面设计已经被分开了。页面模板代码不会收到复杂程序代码的影响。这种分离的思想即便对一个程序员和页面设计师是同一个人的项目来说也都是非常有用的,因为分离使得代码保持简洁而且易于维护。

    尽管FreeMarker也拥有一些编程能力,但通常由Java程序准备要显示的数据,由FreeMarker生成页面,通过模板显示准备的数据(如下图)。

    FreeMarker不是一个Web应用框架,而适合作为Web应用框架的一个组件,但是FreeMarker引擎本身并不知道HTTP协议或Java Servlet的存在。它仅仅来生成文本内容。既然是这样,它也非常适用于非Web应用程序的开发环境。知识要注意的是,我们使用FreeMarker作为视图层的组件,是为了给诸如Struts这样的Model2应用框架提供现成的解决方案,你也可以在模板中使用JSP标记库。

    特性
    通用目标
    易于嵌入到你的产品中,轻量级,不需要Servlet环境

    能够生成各种文本:HTML、XML、RTF、Java源代码等等

    插件式模板载入器,可以从任何源载入模板,如本地文件、数据库等等

    你可以按自己所需生成文本,保存到本地文件,作为Email发送,从Web应用程序发送它返回给Web浏览器

    强大的模板语言
    在模板中创建和改变变量

    命名的宏,可以具有位置参数和嵌套内容

    几乎在任何地方都可以使用复杂表达式来指定值

    所有常用的指令,include、if/elseif/else、循环结构

    名字空间有助于建立和维护可重用的宏库,或者将一个大工程分成模块,而不必担心名字冲突

    输出转换块,在嵌套模板片段生成输出时,转换HTML转义、压缩、语法高亮等等,你可以定义自己的转换

    通用数据模型
    FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量方式在模板中显示

    你可以使用抽象(接口)方式表示对象(JavaBean、XML文档、SQL查询结果集等),告诉模板开发者使用方法,使其不受技术细节的打扰

    为Web准备
    支持JSP标记库

    能够集成到Model2 Web应用框架中作为JSP的替代

    在模板语言中内建处理典型Web相关任务(如HTML转义)的结构

    为MVC模式设计,分离可视化设计和应用程序逻辑,分离页面设计师和程序员

    智能的国际化和本地化
    数字格式本地化敏感

    多种不同语言的相同模板

    日期和时间格式本地化敏感

    字符集智能化(内部使用UNICODE)

    非US字符集可以用作标识(如变量名)

    强大的XML处理能力
    在模板中清楚和直觉的访问XML对象模型

    <#recurse>和<#visit>指令用于递归遍历XML树

    优势
    可以彻底的分离表现层和业务逻辑
    使用JSP开发过程中,在页面中大量的存在业务逻辑代码,使得页面的内容非常混乱,在后期大量的修改维护过程中就变得非常的困难。

    FreeMarker不支持Java脚本代码,FreeMarker的原理是,模板+数据模型=输出。模板只负责数据在页面中的表现,不涉及任何的逻辑代码,而所有的逻辑都是由数据模型来处理的。用户最终看到的输出是模板和数据模型合并后创建的。

    可以提高开发效率
    在以往的开发中,使用的都是JSP页面来展示数据的,即所谓的表现层。我们都知道,JSP在第一次执行的时候需要转换成Servlet类,开发阶段进行功能调试时,需要频繁的修改JSP,每次修改都要编译和转换,那么试想一下,一天中我们浪费在程序编译的时间有多少。

    相对于JSP来说,FreeMarker模板技术不存在编译和转换的问题,所以就不会存在上述问题。而且开发过程中,我们再不必等待界面设计开发人员完成页面原型后,我们再来开发程序。

    而且,一些特定的系统,比如OA工作流系统中,就需要动态生成表单技术,这就为其提供了很好的实现依据。使得在整个流程的进行中,生成不同的表单就简单了很多。
    使得开发过程中的人员分工更加明确
    以往用JSP显示数据时,一些程序员并不熟悉界面设计技术,反之界面开发人员,也并不熟悉程序语言。对两者而言,交替性的工作本身就有难度。有时候稍有不慎,可能会将整个页面元素删除或去掉了某个程序符号,使得页面走样或程序错误,这样就需要双方相互沟通协作,解决出现的问题。有时候因为项目中的时间、任务量等因素的存在,可能这个工作就由一个人来完成,这样就可能加大某一方开发人员的工作量。

    使用FreeMarker后,作为界面开发人员,只专心创建HTML文件、图像以及Web页面的其他可视化方面,不用理会数据;而程序开发人员则专注于系统实现,负责为页面准备要显示的数据。

    不足
    在修改模板后,可能会看到已经过期的数据
    使用FreeMarker模板技术,生成静态的HTML页面后,如果一旦模板改变,而没有及时更新模板生成的HTML页面的话,用户看到的就是过期的数据。 
    FreeMarker的变量必须有值
    FreeMarker模板技术在应用过程中,FreeMarker中的变量必须要赋值,如果不赋值,那么就会抛出异常。FreeMarker没有一个默认的null处理,甚至也不接受一个null值。想避免错误就要应用if/elseif/else 指令进行判段,如果对每一个变量都判断的话,那么则反而增加了编程的麻烦。 
    FreeMarker的Map限定Key必须是String,其他数据类型无法操作
    Map问题,即FreeMarker中不能支持非String的Key值,这样在进行一些复杂迭代时就需要作一些其他的转换,如将一个Map拆分为两个或多个Map。
    FreeMarker不支持集群应用
    为了编成的方便性,把序列化的东西都放到了Session中,如Session,request等,在开发的过程中确实方便,但如果将应用放到集群中,就会出现错误。 

    一个小Demo

    首先,需要下载FreeMarker的jar包,这里提供一个下载链接:freemarker.jar

    然后,将这个freemarker.jar放到Web项目的 WebRoot\WEB-INF\lib 目录下

    最后,我把自己写的测试类贴出来,分享一下。

    FreemarkerTest类 代码

    import java.io.BufferedWriter;  
    import java.io.File;  
    import java.io.FileWriter;  
    import java.io.IOException;  
    import java.io.PrintWriter;  
    import java.util.HashMap;  
    import java.util.Map;  
      
    import freemarker.template.Configuration;  
    import freemarker.template.DefaultObjectWrapper;  
    import freemarker.template.Template;  
    import freemarker.template.TemplateException;  
    import freemarker.template.TemplateExceptionHandler;  
    import junit.framework.TestCase;  
      
    public class FreemarkerTest extends TestCase {  
      
        private String dir = "E:/.../OA/TestTotal/src/com/bjsxt/oa/freemarker";  
      
        public void testFreemarker() {  
            Configuration cfg = new Configuration();  
      
            try {  
                // 从哪里加载模板文件  
                cfg.setDirectoryForTemplateLoading(new File(dir));  
                  
                // 定义模版的位置,从类路径中,相对于FreemarkerManager所在的路径加载模版  
                // cfg.setTemplateLoader(new ClassTemplateLoader(FreemarkerManager.class, "templates"))  
      
                // 设置对象包装器  
                cfg.setObjectWrapper(new DefaultObjectWrapper());  
      
                // 设置异常处理器  
                cfg  
                        .setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);  
      
                // 定义数据模型  
                Map root = new HashMap();  
                root.put("abc", "世界,你好");  
      
                // 通过freemarker解释模板,首先需要获得Template对象  
                Template template = cfg.getTemplate("test.ftl");  
      
                // 定义模板解释完成之后的输出  
                PrintWriter out = new PrintWriter(new BufferedWriter(  
                        new FileWriter(dir+"/out.txt")));  
                  
                try {  
                    // 解释模板  
                    template.process(root, out);  
                } catch (TemplateException e) {  
                    e.printStackTrace();  
                }  
      
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }

    下面是定义的模板 test.ftl


    test.flt 代码
    [html]
    <span style="font-family:Microsoft YaHei;">第一个测试程序:${abc}</span>  

    最后运行的结果如下

    输出了out.txt文件,out.txt文件中的内容如下:
    [html]
    <span style="font-family:Microsoft YaHei;">第一个测试程序:世界,你好</span> 
    ========

    Velocity入门示例


    一、前言                          


      Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力。而且Velocity被移植到不同的平台上,如.Net的NVelocity和js的Velocity.js,虽然各平台在使用和实现上略有差别,但大部分语法和引擎核心的实现是一致的,因此学习成本降低不少哦。

      最好的学习资源——官网:http://velocity.apache.org/

      本系列打算采用如下结构对Velocity进行较为全面的学习,若有不妥或欠缺望大家提出,谢谢。

      1. 入门示例

      2. VTL语法详解

      3. 模板与宿主环境通信

      4. 基础配置项

      5. 深入模板引擎及调优配置

    二、入门示例                        


      示例结果是生成如下的html表单:
    <form action="./submit">
    <div>
      <label for="title">标题:</label>
      <input type="text" id="title" name="title"/>
    </div>
    <div>
      <label for="brief">摘要:</label>
      <input type="text" id="brief" name="brief"/>
    </div>
    <div>
      <label for="sex">性别:</label>
      <select id="sex" name="sex">
        <option value="0">男</option>
        <option value="1">女</option>
      </select>
    </div>
    <div>
      <label for="job">职业:</label>
      <select id="job" name="job">
        <option value="0">Java工程师</option>
        <option value="1">Net工程师</option>
      </select>
    </div>
    </form>

       引入依赖项——velocity-1.7-dep.jar

       模板文件frm.vm

    ##表单模板
    ##@author fsjohnhuang
    ##@version 1.0
    ## 引入外部模板文件
    #parse('macro.vm')
    ## 主逻辑
    <form action="$action">
    #foreach($input in $inputs)
    #input($input.title $input.id)
    #end
    #foreach($select in $selects)
    #select($select.title $select.id $select.items)
    #end
    </form>

       模板文件macro.vm

    ## 生成input表单元素区域的宏
    #macro(input $title $id)
    <div>
      <label for="$id">$title</label>
      <input type="text" id="$id" name="$id"/>
    </div>
    #end
    ## 生成select表单元素区域的宏
    #macro(select $title $id $items)
    <div>
      <label for="$id">$title</label>
      <select id="$id" name="$id">
    ## VTL指令紧贴左侧才能确保结果的排版正常(不会有多余空格)
    #foreach($key in $items.keySet())
        <option value="$key">$items.get($key)</option>
    #end
      </select>
    </div>
    #end

       Java代码:
    public static void main(String[] args) {
            // 初始化模板引擎
            Properties props = new Properties();
            props.put("file.resource.loader.path", ".\\vm");
            VelocityEngine ve = new VelocityEngine(props);
            // 配置引擎上下文对象
            VelocityContext ctx = new VelocityContext();
            ctx.put("action", "./submit");
            ArrayList<HashMap<String, String>> inputs = new ArrayList<HashMap<String,String>>();
            HashMap<String, String> input1 = new HashMap<String, String>();
            input1.put("id", "title");
            input1.put("title", "标题:");
            inputs.add(input1);
            HashMap<String, String> input2 = new HashMap<String, String>();
            input2.put("id", "brief");
            input2.put("title", "摘要:");
            inputs.add(input2);
            ctx.put("inputs", inputs);
            ArrayList<HashMap<String, Object>> selects = new ArrayList<HashMap<String,Object>>();
            HashMap<String, Object> select1 = new HashMap<String, Object>();
            selects.add(select1);
            select1.put("id", "sex");
            select1.put("title", "性别:");
            HashMap<Integer, String> kv1 = new HashMap<Integer, String>();
            kv1.put(0, "男");
            kv1.put(1, "女");
            select1.put("items", kv1);
            HashMap<String, Object> select2 = new HashMap<String, Object>();
            selects.add(select2);
            select2.put("id", "job");
            select2.put("title", "职业:");
            HashMap<Integer, String> kv2 = new HashMap<Integer, String>();
            kv2.put(0, "Java工程师");
            kv2.put(1, "Net工程师");
            select2.put("items", kv2);
            ctx.put("selects", selects);
            // 加载模板文件
            Template t = ve.getTemplate("test.vm");
            StringWriter sw = new StringWriter();
            // 渲染模板
            t.merge(ctx, sw);
            System.out.print(sw.toString());
        }

     Velocity模板引擎使用时的关注点分别为以外部文件形式存在的Velocity模板和Java代码调用。

     Velocity模板由VTL(Velocity Template Language)和引擎上下文对象构成;Java代码调用部分则负责初始Velocity引擎、构建引擎上下文对象、加载Velocity模板和启动模版渲染。而Velocity模板与Java代码调用部分通信的纽带就是引擎上下文对象了。

    三、总结                              


     现在我们对Velocity引擎应该有个大概的了解,后续内容将对上述内容逐一深入。

     如上述示例,若想改成如下格式就要重新设计模板形式了:

    <form action="./submit">
      <div>
        .................
      </div>
      <div>
        .................
      </div>
      <div>
        .................
      </div>
      <div>
        .................
      </div>
    </form>
    ========

    加速Java应用开发速度4-使用模板技术加速项目开发速度


    templateintellij idea代码生成 
    《加速Java应用开发速度》系列目录:
    加速Java应用开发速度1——加速spring/hibernate应用调试时启动速度
    加速Java应用开发速度2——加速项目调试启动速度
    加速Java应用开发速度3——单元/集成测试+CI
    加速Java应用开发速度4——使用模板技术加速项目开发速度
    加速Java应用开发速度5——使用脚本自动化日常操作 
     
    模板,一个我们天天使用的技术, 比如:
    Java类就是创建Java对象实例的模板;
    泛型,参数化类型的模板,如List<User>;常见的如泛型DAO、泛型Service等;
    Arrays.sort() 一个排序的模板;
    Spring中的DAO支持设计;可参考《我对SpringDAO层支持的总结》;
    HttpServlet设计,如service根据http method转发给各个do开头的方法(doGet、doPost等);
    还有如Struts2的ActionSupport设计,我们继承后能自动得到如国际化、验证的支持;
    JSP、Velocity、Freemarker等模板页面技术;
    等等。。
     
    模板带给我们的好处很明显:约束+重用。通过模板我们可以把变与不变分离,重用不变,变可以交给子类/通过回调机制完成,而且还具有约束作用,防止乱写代码。
     
    那我们应该利用好这个技术,加速项目的开发速度。接下来我们看看如何利用模板技术来加速我们的开发速度。
     
    接下来,我将介绍下如何利用模板技术加速开发速度,但不会接受如何利用模板技术开发可复用的代码;本文以IntelliJ IDEA为原型介绍:
    1、代码生成
    2、Live Template
    3、File and code Templates
    4、自动代码生成
     
    1、代码生成

    如我们要求 i + j,可以先:
    int k = add(i, j); 
    然后按Alt+enter自动生成相关代码;而不是自己去敲;其会自动生成相应的变量和方法体;如图
     
    1、按Alt+Enter弹出如创建方法/创建本地变量的提示


    2、可以根据生成的模板方法,修改模板参数;
     
    3、还有典型的如生成构造器、getter/setter等,按Alt+Insert得到如下图:
    比如在生成构造器时,可以选择相关字段,会自动生成相应的赋值操作:
     
    4、Ctrl+O/Alt+Insert生成覆盖方法;ctrl+I/Alt+Insert生成实现方法的


    5、选中代码后,Ctrl+Alt+T弹出Surround with,生成包围代码的的代码,如if/else:


    6、在如JSP标签页面中,按Ctrl+Alt+J,生成环绕的标签:
    等等。。具体可以参考jetbrains.官网的《Generating Code》部分。
     
    2、Live Template

    如果用过idea,肯定用过如输入 psvm 然后按Tab键,会自动生成public static void main方法,使我们少输入很多字符。这个功能在idea中叫做Live Template;接下来让我们看看有哪些Live Template和如何自定义Live Template。
     
    最常见的功能:
    1、psvm----->public static void main(String[] args) {}
    2、psfi ------->public static final int 
    3、ifn   会自动生成如下图

    ……
     
    大家可以通过 File--->Settings---->Live Templates找到所有模板,如:


    如 lazy 生成延迟初始化的代码,inn生成if (** != null)
     
    如fori --->for (int j = 0; j < ; j++) {}    
    List list;  itli  ----> for (int i = 0; i < list.size(); i++) { Object o =  list.get(i);}
        
    等等,这个可以去Settings里查看。
     
    接下来我们看看如何自定义自己的Live Template:
     
    1、输入“缩写前缀”,即在代码中输入的前缀;
    2、模板文本;
    3、Change:选择在哪使用;
     
    接下来在Java文件中,输入test会生成 hello world;
     
    此处看到 $END$ 这种变量,接下来解释下:
    1、格式是$变量名$
    2、$END$ : 表示展开模板后光标停留的位置,比如我们的test,生成模板后,光标停留在hello world 前边;
    3、$SELECTION$ : 表示对选中的代码应用模板;如我们定义test为:
     

     
    此处选中“int i = 1;”,然后按Ctrl+Alt+T 弹出“Surround With” 然后输入test前缀,自动生成:--->hello int i = 1; world 
     
    还有如 输入 if for等前缀 按Ctrl+Shift+Enter会自动生成 带()和{} 的形式,非常方便。
     
    如果想定义自己的变量,可以直接点击在编写模板时,模板文本右边的:“Edit variables” 编写:如得到返回的变量,方法名,类名等等。
     
    大家可以参考官网的《Live Templates》。
     
    3、File and code Templates


    到此我们使用的都是代码块级别的模板,在工作中我们常见的还需要:
    1、生成如Copyright头;
    2、生成类头Javadoc,如用户、时间、版本;
    3、创建如spring配置文件,每次可能都是从别的地方复制一份;
    4、如我们做企业项目时,都先写一个CRUD的DAO、Service、Controller等等,但是几乎每一个模块都是类似的,但是我们每次可能都要重复写一遍;
     
    因为IDEA使用的是velocity模板,所以语法相对来说是比较灵活的,而Eclipse看了下是自创的,所以在创建时没有IDEA灵活。
     
    File---->Settings--->File and Code Templates 打开模板设置页面
     
    3.1、给Class生成Copyright头
    1、创建Include文件


    1、首先选择Includes标签,然后点击创建创建一个;
    2、接着Name处输入名字;
    3、Extension部分输入扩展名;
    4、文件正文;
    此文件包含了我的copyright
     
    2、添加到类模板中

    1、 首先选中Class;
    2、在页面顶部使用 @parse("Coyright.java")把刚才写的文件包含进来
     
    接着新建一个Java Class,在页面的顶部会出现这个copyright;
     
    3.3、自定义Service模板 
    以KeyValueService.java为例。
     
    1、首先File--->Settings---->File and Code Templates进入模板设置页面
    2、创建模板,如图所示
     
    1、点击Templates标签,点击新增按钮
    2、Name处输入名字
    3、Extension处输入扩展名
    4、输入模板正文,此处我们可以使用${NAME}和${PACKAGE_NAME}分别获取输入的文件名和包名
    5、语法是velocity语法,所以很强大,按理说很复杂的需求也可以实现
     
    3、新增Class
    3.1、在包处Alt+Enter弹出新建列表,选择Java  Class
     
    3.2、 接着在弹出的界面中输入 Service的前缀,并选择Service类型:
     
    4、接着类就创建好了:
    Java代码
    /** 
     * Copyright (c) 2005-2012 https://github.com/zhangkaitao 
     * 
     * Licensed under the Apache License, Version 2.0 (the "License"); 
     */  
    package com.sishuok.es.maintain.keyvalue.service;  
      
    import com.sishuok.es.common.inject.annotation.BaseComponent;  
    import com.sishuok.es.common.service.BaseService;  
    import com.sishuok.es.maintain.keyvalue.entity.HelloValue;  
    import com.sishuok.es.maintain.keyvalue.repository.HelloRepository;  
    import org.springframework.beans.factory.annotation.Autowired;  
    import org.springframework.stereotype.Service;  
      
    /** 
     * <p>User: Zhang Kaitao 
     * <p>Date: 13-6-20 下午4:31 
     * <p>Version: 1.0 
     */  
    @Service  
    public class HelloService extends BaseService<Hello, Long> {  
      
        @Autowired  
        @BaseComponent  
        private HelloRepository helloRepository;  
      
    }  
    非常方便。
     
    除此之外,我们还可以定义如 jsp模板、html模板、配置文件模板等等,消除开发中的重复工作。
     
    但是此种方式的主要缺点就是:一次只能一个文件。比如,我们生成Service时,其实DAO、Contoller等最好也自动生成。 接下来就需要自动代码生成了。
     
    4、自动代码生成

    估计很多朋友都使用/开发过代码生成器;其实开发一个简单的代码生成器花不了多长时间,核心指导思想是:变与不变分离:
    不变的是结构;
    变的是包名、类名及实体数据。
    所以根据这个思想可以很容易写一个代码生成器,可以按照如下步骤完成:
    1、自动生成一个模块的DAO、Service、Controller;
    2、根据数据库自动生成一个模块的Entity、DAO、Service、Controller;
    3、自动生成一对一、一对多关系的模块代码;
    4、自动生成公司中常用的代码的模块代码,如树;
    目前见到的主要有这些类型的自动代码生成。
     
    代码生成的模板文件可以使用纯文本(即纯粹的字符串替换),更高级点的可以使用如velocity这种模板语言,更加强大。
     
    如果有朋友不会写代码生成器,可以先建一个Example的示例代码,接着做复制、粘帖、修改实体名等等完成,估计30秒钟也能出一个基本的模块代码。但是如果交给代码生成器,速度更快。
     
    比如笔者刚开发的新项目,还没时间开发代码生成器,就写一个一些示例,这样如果写新模块就直接复制一份改改即可,尤其树这块节省不少时间,点击showcase前往查看。
     
    代码生成器也不是万能的,如果你做过互联网项目,不像企业应用那样有许多的增删改查,所以这个时候,纯粹生成CRUD的代码生成器没有任何意义。
     
    到此我们介绍完了模板,使用模板能提升不少开发速度,但是模板不是万能的,只有发现有些代码是由:变与不变组成,那我们就可以把不变的做成模板,变的部分通过占位符(即变量)填入。
     
    参考资料:
    30 Days with IntelliJ IDEA. Quick Popups
    IntelliJ IDEA 12.1.0 Web Help

    不变的做成模板,变的部分用变量表示。

    是的,这个要自己识别,我们天天在用模板这玩意  ,但是如何识别出现这才是关键。如spring jdbc template就是一个绝好的例子。


    说实话,我们今年的项目中我大胆的用了spring jdbctemplate,可惜,被Manager否定了,说着东西有诸多弊端,没怎么花时间去研究这东西,真心不知道怎么样...楼主能说说看吗,关于原生的JDBC和spring jdbctemplate到底有什么差别么...


    原始的就是写代码多,什么都要自己去写; 
    1、代码冗余
    2、映射部分 
       给PreparedStatement赋值、取值
       ResultSet取值映射到model
    3、事务传播麻烦


    jdbc template就是来干:
    1、消除冗余代码
    2、简化映射
    3、得到spring事务管理的好处

    spring jdbc template 封装的很薄, 其实可以看出个工具, 上手简单、为嘛不用。

    所以直接写jdbc 开发效率慢 而且诸如事务传播等都是很麻烦的。

    你可以参考
    http://jinnianshilongnian.iteye.com/blog/1685963

    你们现在架构是什么样的

    架构,呵呵,谈什么架构哦,以前印度人写的框架给拿过来,算是在此版本上重新开放,基本就是Struts2, Spring(用的很少,基本是我来之后我用的多些), ORM没用框架,基本是自己手写的原生jdbc,经常能看到静态代码监测不通过的,看到数据库连接,resultSet,PreparedStatement未释放的,我当时就像用spring的模板方法肯定剩下很多时间,而且很方便就搞定了,

    可以不用spring/hibernate等框架 但是得借用相关的思想; 所以你说的那些根本没 “不重复自己”。楼主可以写一套重构 给经理看: 节省了代码就节省了开发时间;代码少了 bug自然就会少


    jdbc template就是来干:
    1、消除冗余代码
    2、简化映射
    3、得到spring事务管理的好处


    所以直接写jdbc 开发效率慢 而且诸如事务传播等都是很麻烦的。

    是少了个On frame deactivation 这个设置

    我现在都是maven+jetty运行,还真没有这么用过,刚查了下
    http://stackoverflow.com/questions/9117844/publish-changes-to-server-from-intellij-idea-11

    意思就是当idea窗口失去焦点焦点时 更新 

    哦 我刚才在Build Artifacts 添加上项目artifacts的就出现窗口和hot swap classes选项了
    和现在我还不知道这个Artifacts 是干什么的?? 现在每次更新都需要按下反人类的快捷键CTRL+SHIFT+F9更新 如果有跟ECLIPSE的保存自动更新就方便了 目前没找到


    Artifacts 就是制品,可以理解为打包好的产品;比如打成war包 / 直接的web项目目录 / jar包


    不过按你说的On frame deactivation,配置这个应该是可以的; 如果maven内嵌 我现在也是CTRL+SHIFT+F9更新


    哦 我刚才在Build Artifacts 添加上项目artifacts的就出现窗口和hot swap classes选项了
    和现在我还不知道这个Artifacts 是干什么的?? 现在每次更新都需要按下反人类的快捷键CTRL+SHIFT+F9更新 如果有跟ECLIPSE的保存自动更新就方便了 目前没找到


    如果是非debug模式下


    http://jinnianshilongnian.iteye.com/blog/1887788


    如在执行tomcat/jetty时的VM参数中指定如上配置即可。无需在debug模式下执行。如果使用的是如idea可以按Ctrl+Shift+F9编译当前类/Ctrl+F9编译所有更改的类。


    我之前调试成功过一次 就是在TOMCAT 设置那有2个On 'Update' action xx 的选项
    都选择hotswap class 然后在每次修改代码后在Deployment选项卡点Deploy All就完成你说的热替换效果 但是现在不知道为什么现在On 'Update' action只有1个地方设置 选项里面也只有Restart server选项 咋也变不回去了


    如在执行tomcat/jetty时的VM参数中指定如上配置即可。无需在debug模式下执行。如果使用的是如idea可以按Ctrl+Shift+F9编译当前类/Ctrl+F9编译所有更改的类。


    所以你可以参考这篇 直接上字节码热替换 


    我09年接触过,那时候也是抵触,直到最近一年 尤其maven集成这块 eclipse比较弱,使用idea真心方便不少。
    ========

    eclipse开发velocity实例


    开发环境

            Eclipse Java EE IDE for Web Developers.(Version: Helios Service Release 1)

            jdk1.6.0_07

            apache-tomcat-6.0.10

            首先需要为eclipse安装velocity编辑插件:

                    在Eclipse的Help->Install New Software...

                    点击“Add加 Veloeclipse,值:http://veloeclipse.googlecode.com/svn/trunk/update/


                    备注:如果无法安装,在安装插件的界面中把 Group Items by Catagory 前打钩去掉


    创建项目


            打开eclipse,File -> New -> Other... -> Web -> Dynamic Web Projec  之后按照提示一路next;


            注意1:修改代码文件输出路径,默认是build\classes;这个不符合tomcat的要求,需要改成WebContent\WEB-INF\classes 如下:
            
            注意2:勾选 Generate web.xml deployment descriptor


            添加velocity依赖包


                    velocity相关的包有十多个,下载解压后复制全部,粘贴到项目WebContent -> WEB-INF ->lib文件夹中


                    项目名右键菜单选择build path -> configure build path ->java build path ->libraries -> add jars 选择项目lib文件夹,选择全部jar最后点确认按钮
                    备注:velocity所需的jar包下载地址 http://download.csdn.net/detail/smilevt/4802244

            添加velocity模板文件

                    在WebContent下添加一个文件夹,我起名叫templates。在该文件下添加一个hello.vm文件,里面内容如下:
                    <html>
                            <head><title>Sample velocity page</title></head>
                            <body bgcolor="#ffffff">
                                    <center>
                                            <h2>Hello My First Velocity</h2>
                                            <table width="100" cellpadding="5" cellspacing="1" bordercolor="#333333">
                                                    <tr><td bgcolor="#eeeeee" align="center">name list</td></tr>
                                                    #foreach ($name in $theList)
                                                    <tr><td bgcolor="#6666FF" align="center">$name</td></tr>
                                                    #end
                                            </table>
                                    </center>
                            </body>
                    </html>




            添加java类文件

                    在Java Resource -> src下新建一个包,包名com.velocitydemo.velocityhandler,名称随意,后面配置文件里要用到,前后需要一致

                    在该包下添加一个类,类名HelloHandler,名称随意,道理同上

                    类的代码如下:

                    package com.velocitydemo.velocityhandler;

                    import java.util.Properties;

                    import java.util.Vector;

                    import javax.servlet.ServletException;

                    import javax.servlet.http.HttpServletRequest;

                    import javax.servlet.http.HttpServletResponse;

                    import org.apache.velocity.Template;

                    import org.apache.velocity.app.Velocity;

                    import org.apache.velocity.app.VelocityEngine;

                    import org.apache.velocity.context.Context;

                    import org.apache.velocity.tools.view.VelocityViewServlet;

                    public class HelloHandler extends VelocityViewServlet {

                            private static final long serialVersionUID = 1L;

                            private VelocityEngine velo;

                            public void init() throws ServletException {

                                    velo = new VelocityEngine();// velocity引擎对象

                                    Properties prop = new Properties();// 设置vm模板的装载路径

                                    String path = this.getServletContext().getRealPath("/");

                                    prop.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path + "templates/");

                                    try {

                                            velo.init(prop);// 初始化设置,下面用到getTemplate("*.vm")输出时;一定要调用velo对象去做,即velo.getTemplate("*.vm")

                                    } catch (Exception e1) {

                                            e1.printStackTrace();

                                    }

                            }

                            protected Template handleRequest(HttpServletRequest request, HttpServletResponse response, Context ctx) {

                                    String p1 = "Charles";

                                    String p2 = "Michael";

                                    Vector personList = new Vector();

                                    personList.addElement(p1);

                                    personList.addElement(p2);

                                    ctx.put("theList", personList); // 将模板数据 list放置到上下文环境context中

                                    Template template = velo.getTemplate("hello.vm");

                                    return template;

                            }

                    }               


            配置web.xml

                    打开WEB-INF下的web.xml文件,内容如下:

                    <?xml version="1.0" encoding="UTF-8"?>

                    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                       xmlns="http://java.sun.com/xml/ns/javaee"
                                       xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
                                       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
                                       id="WebApp_ID" version="3.0">

                            <display-name>velocity-demo</display-name>

                            <servlet>
                                    <servlet-name>velocity</servlet-name>
                                    <servlet-class>org.apache.velocity.tools.view.VelocityViewServlet</servlet-class>
                            </servlet>
                            <servlet-mapping>
                                    <servlet-name>velocity</servlet-name>
                                    <url-pattern>*.vm</url-pattern>
                            </servlet-mapping>

                            <servlet>
                                    <servlet-name>hello</servlet-name>
                                    <servlet-class>com.velocitydemo.velocityhandler.HelloHandler</servlet-class>
                            </servlet>
                            <servlet-mapping>
                                    <servlet-name>hello</servlet-name>
                                    <url-pattern>/hello</url-pattern>
                            </servlet-mapping>

                            <welcome-file-list>
                                    <welcome-file>/hello</welcome-file>
                            </welcome-file-list>


                    </web-app>

            至此一个最简单的velocity项目创建完毕

                    注意:WEB-INF下的classes文件夹并不显示在项目结构图中
                    
            发布项目

                    到这里可以把项目文件夹中的WebContent发布到Tomcat中进行测试了

                    发布的方式参考地址(http://blog.csdn.net/smilevt/article/details/8212075)

            启动tomcat访问一个最简单的velocity项目
    ========
    展开全文
    bcbobo21cn 2016-03-30 18:54:27
  • 基于python和opencv实现信用数字识别 本项目和源码来源于唐宇迪opencv项目实战 1.准备工作 python3.5 opencv4.2 同一字体,相似类型的信用图片 字体模板图片 2.本项目总体可分为两部分: 对模板图像的处理 对...

    基于python和opencv实现信用卡数字识别

    本项目和源码来源于唐宇迪opencv项目实战

    1.准备工作

    • python3.5
    • opencv4.2
    • 同一字体,相似类型的信用卡图片
    • 字体模板图片信用卡图片字体模板图片

    2.本项目总体可分为两部分:

    • 对模板图像的处理
    • 对信用卡图像的处理

    3.应用到的数字图像处理基础知识

    • 图像灰度处理
    • 图像二值化处理
    • 检测轮廓,画出轮廓
    • 画图形的外接矩形
    • 图像的形态学操作

    4.先放代码, 我把每一步生成的图片都显示出来,以便观察每一步操作所起的作用

    # 导入工具包
    from imutils import contours
    import numpy as np
    import argparse
    import cv2
    import myutils
    
    
    # 设置参数
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True,
    	help="path to input image")
    ap.add_argument("-t", "--template", required=True,
    	help="path to template OCR-A image")
    args = vars(ap.parse_args())
    
    # 指定信用卡类型
    FIRST_NUMBER = {
        "3": "American Express",
        "4": "Visa",
    	"5": "MasterCard",
    	"6": "Discover Card"
    }
    # 参数为读取的图像image和模板图像template
    template = cv2.imread(args["template"])
    # 将模板转化为灰度图
    template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
    # 将模板转化为二值图像
    # threshold函数应该返回两个值,第二个值为输出图像,所以有[1]
    # threshold操作,超过thresh的部分取最大值,否则取零,然后再对图像进行反转
    template_thresh = cv2.threshold(template_gray, 10, 255, cv2.THRESH_BINARY_INV)[1]
    
    
    # 计算轮廓,findcontours()函数只接受二值图像作为参数
    refCnts, hierarchy = cv2.findContours(template_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    template_c = template.copy()
    # 参数resCnts表示存储在列表中的提取到的所有轮廓,参数-1表示默认画出所有轮廓
    template_draw = cv2.drawContours(template_c, refCnts, -1, (255, 170, 0), 3)
    print('refCnts', np.array(refCnts).shape)
    # 轮廓的排序方式,从左到右,从上到下
    # 对轮廓进行排序, 为了将轮廓和对应的数字匹配,即在图像中与数字的模板匹配则显示对应的数字结果
    # 根据外接矩形返回的值,比较横坐标大小,对轮廓进行排序
    refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0]
    # 新建一个空字典
    digits = {}
    # 用循环遍历每一个轮廓
    # 此时轮廓的列表中内容已排序完成,
    # i表示下标,c表示i对应数值的模板的图像
    for (i, c) in enumerate(refCnts):
        # 计算外接矩形并且resize成合适大小
        (x, y, w, h) = cv2.boundingRect(c)
        # 用外接矩形当做每一个数字的模板
        # 利用外接矩形返回的值制作出每一个数字的模板
        roi = template_thresh[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        # 每个数字对应一个模板
        digits[i] = roi
        cv2.imshow("roi", roi)
        cv2.waitKey(100)
    
    # 初始化卷积核
    rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
    sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    
    #读取输入的图像进行预处理,进行一系列形态学操作
    image = cv2.imread(args["image"])
    image_resize = myutils.resize(image, width = 300)
    image_gray = cv2.cvtColor(image_resize, cv2.COLOR_BGR2GRAY)
    
    # 礼帽操作,突出明亮的区域,滤除部分干扰信息
    # 其中卷积核的大小由要保留的字体大小的具体情况决定
    image_tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rectKernel)
    #ksize=-1相当于用3*3的,锐化操作
    image_sobel = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0,
    	ksize=-1)
    
    gradX = np.absolute(image_sobel)
    (minVal, maxVal) = (np.min(gradX), np.max(gradX))
    gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
    gradX = gradX.astype("uint8")
    print ("gradx.shape", np.array(gradX).shape)
    # 通过闭操作(先膨胀,再腐蚀)将数字连在一起
    image_close = cv2.morphologyEx(
        gradX, cv2.MORPH_CLOSE, rectKernel)
    #THRESH_OTSU会自动寻找合适的阈值,适合双峰,让opencv自动判断阈值,所以第二个参数为0
    image_thresh = cv2.threshold(image_close, 0, 255,
    	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 再进行一个闭操作
    image_close_2 = cv2.morphologyEx(image_thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作
    
    
    # 计算轮廓
    threshCnts, hierarchy = cv2.findContours(image_close_2.copy(), cv2.RETR_EXTERNAL,
    	cv2.CHAIN_APPROX_SIMPLE)
    
    cnts = threshCnts
    cur_img = image_resize.copy()
    cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
    locs = []
    # 遍历轮廓
    # 希望只保留银行卡数字部分的轮廓,将满足长宽比的轮廓保留
    for (i, c) in enumerate(cnts):
        # 计算矩形
        (x, y, w, h) = cv2.boundingRect(c)
        ar = w / float(h)
    
        # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
        if ar > 2.5 and ar < 4.0:
    
            if (w > 40 and w < 55) and (h > 10 and h < 20):
                #符合的留下来
                locs.append((x, y, w, h))
    print('locs', len(locs))
    # 将符合的轮廓从左到右排序
    locs = sorted(locs, key=lambda x:x[0])
    output = []
    
    # 遍历每一个轮廓中的数字
    for (i, (gX, gY, gW, gH)) in enumerate(locs):
        # initialize the list of group digits
        groupOutput = []
        # 从灰度图像中 将每一个数字分割开
        group = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        group_thresh = cv2.threshold(group, 0, 255,
            cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        cv2.imshow("groupthresh", group_thresh)
        cv2.waitKey(1000)
        # 计算每一组的轮廓
        digitCnts, hierarchy = cv2.findContours(group_thresh.copy(), cv2.RETR_EXTERNAL,
                                                        cv2.CHAIN_APPROX_SIMPLE)
        # 再对每一组轮廓进行排序
        digitCnts = contours.sort_contours(digitCnts,
            method="left-to-right")[0]
        # 计算每一组中的每一个数值
        for c in digitCnts:
            # 找到当前数值的轮廓,resize成合适的的大小
            (x, y, w, h) = cv2.boundingRect(c)
            roi = group[y:y + h, x:x + w]
            roi = cv2.resize(roi, (57, 88))
            cv2.imshow("roi_r", roi)
            cv2.waitKey(500)
            # 计算匹配得分
            scores = []
            # 在模板中计算每一个得分
            for (digit, digitROI) in digits.items():
                # 模板匹配
                result = cv2.matchTemplate(roi, digitROI,
                                           cv2.TM_CCOEFF)
                #返回四个值min,max,minlocation, maxlocation
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)
    
            # 得到最合适的数字
            # 函数np.argmax()作用是输出最大值所对应的索引
            # 而索引与模板表示的值相对应
            groupOutput.append(str(np.argmax(scores)))
        # 画出轮廓
        image_loc = image_resize.copy()
        cv2.rectangle(image_resize, (gX - 5, gY - 5), (gX+gW+5, gY+gH +5), (0, 0, 255), 2)
        cv2.putText(image_resize, "".join(groupOutput), (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (13, 158, 153), 2)
        # 得到结果
        output.extend(groupOutput)
    
    
    # 打印结果
    print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
    print("Credit Card #: {}".format("".join(output)))
    
    
    cv2.imshow("1template", template)
    cv2.imshow("2template_gray", template_gray)
    cv2.imshow("3template_thresh", template_thresh)
    cv2.imshow("4template_draw", template_draw)
    cv2.imshow("5original_image", image)
    cv2.imshow("6image_gray", image_gray)
    cv2.imshow("7image_tophat", image_tophat)
    cv2.imshow("8image_sobel", image_sobel)
    cv2.imshow("9gradX", gradX)
    cv2.imshow("10image_close", image_close)
    cv2.imshow("11image_thresh", image_thresh)
    cv2.imshow("12image_close_2",image_close_2)
    cv2.imshow("13cur_img", cur_img)
    cv2.imshow("14group", group)
    cv2.imshow("15group_thresh", group_thresh)
    cv2.imshow('16roi', roi)
    cv2.imshow("Image_loc", image_loc)
    cv2.imshow("gradX", gradX)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    # myutils.py
    import cv2
    
    def sort_contours(cnts, method="left-to-right"):
        reverse = False
        i = 0
    
        if method == "right-to-left" or method == "bottom-to-top":
            reverse = True
    
        if method == "top-to-bottom" or method == "bottom-to-top":
            i = 1
        boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
        (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                            key=lambda b: b[1][i], reverse=reverse))
    
        return cnts, boundingBoxes
    def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
        dim = None
        (h, w) = image.shape[:2]
        if width is None and height is None:
            return image
        if width is None:
            r = height / float(h)
            dim = (int(w * r), height)
        else:
            r = width / float(w)
            dim = (width, int(h * r))
        resized = cv2.resize(image, dim, interpolation=inter)
        return resized
    

    5.代码实现步骤如图所示

    代码实现步骤

    2.1 处理模板图片

    处理模板图片最终目的是用每一个数字的模板,与待识别的信用卡图像进行模板匹配,最后识别出具体的数字。
    我们要做的就是把模板图片中的每一个数字分割开,并把每一个数字图像与其所代表的值对应起来。

    第一步 提取轮廓

    使用opencv中 的findContours()函数检测轮廓,由于该函数只能输入二值图像,所以用cvtcolor(img, cv.GBR2GRAY)函数将图像转化为灰度图,再用thresh()函数将图像转化成二值图像。由于thresh函数有两个返回值,我们需要的只有返回的二值图像,所以在程序中该条语句的末尾加上了[1],就是将第二个返回值传递给了img_thresh。
    使用cv2.drawContours()函数画出轮廓,参数-1表示画出所有轮廓,在绘制轮廓,要将原图复制,否则原图会被更改。
    绘制轮廓结果如图所示
    轮廓检测

    第二步 轮廓排序与数字匹配

    提取到的轮廓都保存在了refCnts中,但我们想要轮廓以0数字的轮廓、1数字的轮廓、2数字的轮廓…9数字的轮廓的顺序排列,以便后续将模板与数字对应。
    在自己定义的myutils.py ,resize函数中,前面已经得到了图像中的所有轮廓,resize()函数中做出每一个轮廓的外接矩形,有4个返回值,前两个返回值表示外接矩形左上角的点。由于本实例中模板图像中数字横向排列,我们只需提取出每个轮廓外接矩形左上角点的横坐标,从小到大,即从左到右进行排列。
    建立一个空的字典,在循环中枚举所有轮廓和其对应的索引,之前已经返回了每一个轮廓,在循环中做每一个小轮廓的外接矩形,返回值是每个小轮廓的外接矩形左上角的坐标点和矩形的宽和高。

    for (i, c) in enumerate(refCnts):
        # 计算外接矩形并且resize成合适大小
        (x, y, w, h) = cv2.boundingRect(c)
        # 用外接矩形当做每一个数字的模板
        # 利用外接矩形返回的值制作出每一个数字的模板
        roi = template_thresh[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        # 每个数字对应一个模板
        digits[i] = roi
        cv2.imshow("roi", roi)
        cv2.waitKey(100)
    

    图像切片的操作就是把模板图像中的每一个数字单独抠出来,变成单独的一张图片,接着对每一张小的图片resize(),这里没有特别要求,建议电脑屏幕放得下就行。但是这个大小后面还会用到,digits是刚刚定义的一个空的字典,因为前面已经对轮廓排好了顺序,这里只需要将这些轮廓依照下标索引放进digits中,然后在整个程序运行的时候,会一次从0到9显示每张小图片。waitKey()参数设置成100了。
    到这里就处理完模板图片了。

    2.2 处理信用卡图片

    信用卡图片的处理比处理模板麻烦一丢丢。

    第一步 读取信用卡图像进行一系列的形态学操作

    在程序中首先定义了两个卷积核,用于后续的TOPHAT顶帽操作、和形态学闭操作。resize()后width=300,则整张图按照原比例缩小。
    顶帽操作突出明亮区域,滤除干扰信息。sobel算子滤波进行锐化,本实例中的sobel只对水平方向进行滤波。结果如图所示
    水平方向锐化滤波结果
    进行二值化的程序中用到了THRESH_OTSU(大津法) 这是一种图像二值化的方法,又称为最大类间方差法。这种方法使得前景和背景类间方差最大。这种方法适合双峰,能够自动寻找合适的阈值,第二个参数设为0,并不是将阈值设为0,而是让opencv自动检测阈值。
    https://www.cnblogs.com/ranjiewen/p/6385564.html

    image_thresh = cv2.threshold(image_close, 0, 255,
    	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    

    做形态学闭操作,相邻较近 的图像连在一起,根据信用卡数字的特征将每四个数字连成一个整体,再进行二值化。结果如图所示
    形态学闭操作结果
    我们关注的是数字部分的图像,为填补数字部分图像的空洞,确保数字部分连成一个整体,再进行一次形态学闭操作,结果如图所示:
    第二次形态学闭操作结果

    第二步 只保留数字部分的图像,滤除其他干扰

    二值化以后的图像进行轮廓检测画出图像,步骤与模板图像的轮廓检测类似。但此时图像中不仅有数字部分图像还有其他的图案。我们只关注数字部分的图像确切地说是四个数字连在一起的那部分图像,要将数字图像与其他图像分离,就要找出我们要保留的部分有什么特征。它们可以看做是长宽比固定的矩形,根据这一特征我们将数字部分的图像与干扰图像分割开,滤除干扰图像。
    做出每一个提取到的轮廓的外接矩形,并且计算每一个外接矩形的长宽比,如果满足条件则是我们希望保留的数字部分的图像。将数字部分的(四个数字连成一组的)图像外接矩形保存在locs中,这里我们打印了一下len(locs),如果为4则正确。
    同样按照横坐标从左到右的方法对locs中的外接矩形进行排序
    接着遍历locs中的每一个外接矩形,对每一个外接矩形进行进一步操作。因为此时每一个外接矩形内部都包含4个数字。我们对resize 后的源图像进行操作。上一步保存了四个外接矩形的定位点和长宽值。
    这段程序是对四个数字一组的轮廓标记在resize后的灰度图上,此时应该用切片提取出这四个图像。此时(发现上一步进行的一系列操作就是为了返回四个外接矩形信息,用来进一步提取出每一个数字的图像)

    for (i, (gX, gY, gW, gH)) in enumerate(locs):
        # initialize the list of group digits
        groupOutput = []
        # 从灰度图像中 将每一个数字分割开
        group = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        group_thresh = cv2.threshold(group, 0, 255,
            cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        cv2.imshow("groupthresh", group_thresh)
        cv2.waitKey(1000)
    

    运行结果如图(这里只显示其中的一张图片):
    上一步的轮廓截取的数字图片
    接下来终于到了最后一层。对于上一步的到的四个数字组成的图像,首先对其进行轮廓检测,画出轮廓, 同样的,得到的轮廓我们要用的是它的返回值,利用这个返回值进行对检测到的轮廓按照横坐标进行排序,同样利用轮廓的返回值检测外接矩形(此时外接矩形中 只包含一个数字了。)
    还记得在分割模板数字图像时resize的大小吗?没错现在需要把分割完的图像也resize成与模板图像相同的大小(57, 88)

    第三步 进行模板匹配

    现在我们得到了每一个数字的模板图片和信用卡图片中的每一个数字的图片
    模板匹配的程序段

    scores = []
            # 在模板中计算每一个得分
            for (digit, digitROI) in digits.items():
                # 模板匹配
                result = cv2.matchTemplate(roi, digitROI,
                                           cv2.TM_CCOEFF)
                #返回四个值min,max,minlocation, maxlocation
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)
    

    模板匹配选择较大结果还是较小的结果与选择的方法有关。共有6种方法,本实例中选择的是cv2.TM_CCOEFF,该方法计算相关系数,值越大越相关。
    最后,将resize后的原图像上画出每一个数字的外接矩形轮廓(这里外接矩形向外延伸了5个像素点)
    在图像上外接矩形上方15个像素点的位置添加文字

    join()方法——用于将序列中的元素以指定的字符连接生成一个新的字符串
    join()方法语法:str.join(sequence),sequence为要连接的元素序列。 一种格式化字符串的函数
    str.format() format 函数可以接受不限个参数,位置可以不按顺序。

    https://blog.csdn.net/weixin_41874898/article/details/99624454
    最后现实的结果如图所示:

    显示结果

    补充内容:
    这里用python切片操作对图像进行分割,这里切片操作纵坐标在前,横坐标参数在后
    在这里还验证了一下参数的位置

    import cv2
    img = cv2.imread("xxx.jpg")
    print(img.shape)
    qiepian = img[0:100, 100:200]
    qiepian_1 = img[100:200, 0:100]
    cv2.imshow("original", img)
    cv2.imshow("qiepian", qiepian)
    cv2.imshow("qiepian_1", qiepian_1)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    因为切片从开始就一定是在图像的边缘,运行结果如图所示:
    验证切片操作
    因为shape()函数返回的就是(h, w, c)嘛
    imshow(“qiepian”, qiepian)截取的是眼睛那部分,纵坐标从0开始,验证完毕,切片操作参数是纵坐标在前横坐标在后。黄轩好帅:)

    至此,信用卡数字识别内容完成

    武汉加油!

    展开全文
    weixin_43227526 2020-03-05 00:20:33
  • 我们在超级列表框里增加一个对辅助进度环节的监控反馈,它同时监控几个窗口,同时录制了线程结束的部分。 监控反馈及线程结束源码: ....计次循环尾 () 源码:大漠多线程循环任务自动切换模板-5监控反馈及线程结束

    我们在超级列表框里增加一个对辅助进度环节的监控反馈,它同时监控几个窗口,同时录制了线程结束的部分。

    监控反馈及线程结束源码:

    .版本 2
    .支持库 iext
    .支持库 EThread
     
    .子程序 监控反馈, , , 反馈线程执行情况
    .参数 索引, 整数型, , 主线程的索引
    .参数 事件, 文本型
     
    窗口列表.置标题 (索引 - 1, 4, 事件)
     
    .子程序 停止, , , 还原状态,解绑窗口,结束线程,
    .局部变量 索引
     
    按钮_开始.标题 = “全部开始”
    超级延时 (1000, )
    ' 要根据游戏窗口的数量来完成窗口的操作
    .计次循环首 (窗口列表.取表项数 (), 索引)
        线程 [索引].运行状态 = 0
        监控反馈 (索引, “窗口任务已停止”)
        ' 开始解绑
        线程 [索引].dm.UnBindWindow ()
        ' 还原一下窗口状态
        线程 [索引].dm.SetWindowState (到整数 (窗口列表.取标题 (索引 - 1, 3)), 5)
        ' 结束一下线程句柄,释放资源
        强制结束线程 (线程 [索引].线程句柄)
    .计次循环尾 ()

    源码:大漠多线程循环任务自动切换模板-5监控反馈及线程结束

    展开全文
    zcp528 2020-06-03 09:10:57
  • ly2983068126 2018-04-12 13:34:21
  • 635KB jjk_02027 2016-12-30 10:23:50
  • weixin_39681161 2020-12-20 18:18:24
  • baidu_41601299 2019-03-12 12:12:13
  • lih062624 2017-04-06 11:33:56
  • jongde1 2019-08-28 18:16:09
  • 21KB weixin_38744153 2019-10-25 00:35:42
  • yuanziok 2021-12-02 09:37:43
  • weixin_48267104 2020-10-17 23:28:11
  • qq_28506819 2017-09-15 00:02:46
  • weixin_44907813 2019-08-11 11:15:44
  • 90KB weixin_42114580 2021-05-24 20:58:58
  • sonoface 2019-07-14 17:31:27
  • qq21497936 2017-12-12 00:09:55
  • angiecat520 2018-09-19 12:50:50
  • qq_27930635 2018-11-28 10:27:48
  • 张容铭 2020-03-18 15:43:47
  • weixin_41423450 2018-11-23 10:27:31
  • liuzehn 2021-07-22 21:41:06
  • XTQueen_up 2013-08-07 09:15:32
  • qq_41405475 2021-03-20 16:01:44

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 37,274
精华内容 14,909
关键字:

任务卡模板