微信公眾號(hào)(服務(wù)號(hào))開(kāi)發(fā)-代碼編寫

本人使用java開(kāi)發(fā)

框架:springboot 1.5.7.RELEASE

本次項(xiàng)目所用的服務(wù)器已有其他項(xiàng)目在運(yùn)行记舆,并且占用了443端口涨薪。原項(xiàng)目使用nginx實(shí)現(xiàn)轉(zhuǎn)發(fā)芯肤,所以需要重新配置nginx增加域名轉(zhuǎn)發(fā)潜支,以滿足微信必須使用443端口要求蒸矛。
原nginx配置文件如下

user  www www;
worker_processes auto;
error_log  /www/wwwlogs/nginx_error.log  crit;
pid        /www/server/nginx/logs/nginx.pid;
worker_rlimit_nofile 51200;

events
    {
        use epoll;
        worker_connections 51200;
        multi_accept on;
    }

http
    {
        include       mime.types;
    include proxy.conf;
    #include luawaf.conf;

        default_type  application/octet-stream;

        server_names_hash_bucket_size 512;
        client_header_buffer_size 32k;
        large_client_header_buffers 4 32k;
        client_max_body_size 50m;

        sendfile   on;
        tcp_nopush on;

        keepalive_timeout 60;

        tcp_nodelay on;

        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 64k;
        fastcgi_buffers 4 64k;
        fastcgi_busy_buffers_size 128k;
        fastcgi_temp_file_write_size 256k;
        fastcgi_intercept_errors on;

        gzip on;
        gzip_min_length  1k;
        gzip_buffers     4 16k;
        gzip_http_version 1.1;
        gzip_comp_level 2;
        gzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
        gzip_vary on;
        gzip_proxied   expired no-cache no-store private auth;
        gzip_disable   "MSIE [1-6]\.";

        limit_conn_zone $binary_remote_addr zone=perip:10m;
        limit_conn_zone $server_name zone=perserver:10m;

        server_tokens off;
        access_log off;

server
    {
        listen 888;
        server_name www.bt.cn;
        index index.html index.htm index.php;
        root  /www/server/phpmyadmin;

        #error_page   404   /404.html;
        include enable-php.conf;

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

        location ~ /\.
        {
            deny all;
        }

        access_log  /www/wwwlogs/access.log;
    }
include /www/server/panel/vhost/nginx/*.conf;
}

\color{red}{注意:}配置文件最下面 include 這行內(nèi)容娄徊,會(huì)將*.conf文件全部引入到配置中闽颇。所以我們?cè)?/www/server/panel/vhost/nginx/ 路徑下新增本次項(xiàng)目所需配置,如下

server
{
    listen 80;
    listen 443 ssl;
    server_name 項(xiàng)目域名;
    root /根路徑;
    
    #SSL-START SSL相關(guān)配置,請(qǐng)勿刪除或修改下一行帶注釋的404規(guī)則
    #error_page 404/404.html;
    #HTTP_TO_HTTPS_START
    if ($server_port !~ 443){
        rewrite ^(/.*)$ https://$host$1 permanent;
    }
    #HTTP_TO_HTTPS_END
    ssl_certificate    /ssl證書.crt;
    ssl_certificate_key    /ssl證書.rsa;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    error_page 497  https://$host$request_uri;

    #SSL-END
    
    #REWRITE-START URL重寫規(guī)則引用,修改后將導(dǎo)致面板設(shè)置的偽靜態(tài)規(guī)則失效
    #REWRITE-END
    
    location = /xxx.txt{
        
    }
    
    location / {
                    proxy_pass https://項(xiàng)目域名:項(xiàng)目端口;
            }
    
    access_log  /www/wwwlogs/項(xiàng)目域名.log;
    error_log  /www/wwwlogs/項(xiàng)目域名.error.log;
}

配置文件中的 \color{red}{項(xiàng)目域名寄锐、根路徑兵多、ssl證書、項(xiàng)目端口} 需要替換成自己的內(nèi)容橄仆。
location中的xxx.txt 是在上一篇中說(shuō)的配置授權(quán)域名時(shí)的校驗(yàn)文件剩膘,將校驗(yàn)文件放置根目錄下既可以訪問(wèn)到。

1.獲取openid

本次項(xiàng)目集成了微信支付盆顾、公眾號(hào)模板消息怠褐,需要使用到用戶openid,所以首先獲取openid您宪。
配置公眾號(hào)菜單 打開(kāi) https://項(xiàng)目域名/open/openid 地址奈懒。
該接口重定向至微信授權(quán)地址奠涌,只需要openid所以使用用戶無(wú)感的 scope=snsapi_base 即可。
代碼如下

@Controller
@Slf4j
public class LoginUserController {

    @Resource
    private ICourseService courseService;

    @Resource
    private MyWXPayConfig myWXPayConfig;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private IWxAccessTokerService accessTokerService;

    @Resource
    private CourseLocationMapper locationMapper;

    @Autowired
    private IDataDictionaryService dataDictionaryService;

    @Value("${server.port}")
    private String port;

    @Value("${wxconfig.domain}")
    private String domain;

    @RequestMapping("/open/openid")
    public void getOpenId(HttpServletRequest request, HttpServletResponse response){
        StringBuffer sb = new StringBuffer();
        StringBuffer encodeUrl = new StringBuffer("https://");
        //公眾號(hào)中配置的回調(diào)域名(網(wǎng)頁(yè)授權(quán)回調(diào)域名)
        String root = request.getContextPath();
        String appId = myWXPayConfig.getAppID();

        sb.append("https://open.weixin.qq.com/connect/oauth2/authorize?appid=");
        sb.append(appId);
        try {
            //對(duì)重定向url進(jìn)行編碼筐赔,官方文檔要求
            encodeUrl.append(domain).append(root)/*.append(":").append(port)*/;

            String url = URLEncoder.encode(encodeUrl.toString(), "utf-8");
            sb.append("&redirect_uri=").append(url);

            //網(wǎng)頁(yè)授權(quán)的靜默授權(quán)snsapi_base
            sb.append("&response_type=code&scope=snsapi_base#wechat_redirect");
            response.sendRedirect(sb.toString());
        } catch (UnsupportedEncodingException e) {
            log.error("重定向url編碼失斚承伞:>>" + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            log.error("response重定向失敗:>>" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * @Title: login
     * @Description: TODO(首頁(yè))
     * @param @return    設(shè)定文件
     * @return String    返回類型
     * @throws
     */
    @RequestMapping("/")
    public String login(Model model, HttpServletRequest request, HttpServletResponse response) throws BusinessException {

        //獲取重定向攜帶的code參數(shù)值
        String code = request.getParameter("code");

        String currentPageUrl = request.getRequestURL().toString();

        if(StringUtils.isNotBlank(code)) {

            currentPageUrl = new StringBuffer("https://").append(domain).append("/").append("?code=").append(code).append("&state=").toString();

            String openId = null;

            if (null == openId) {
                /*
                 * 根據(jù)得到的code參數(shù)茴丰,內(nèi)部請(qǐng)求獲取openId的方法达皿。
                 */
                openId = getOpenId(request,code);
            }

            /*
             * 將openId保存到session中,當(dāng)其他業(yè)務(wù)獲取openId時(shí)贿肩,
             * 可先從session中獲取openId.
             */
            request.getSession().setAttribute("openid", openId);
        }
        try {
            String jsapi_ticket = accessTokerService.getWeiXinTicket();
            Map<String, String> ret = WxJsapiSignUtil.sign(jsapi_ticket, currentPageUrl);
            Map<String, String> wxconfig = new HashMap<>();
            wxconfig.put("appId", myWXPayConfig.getAppID());
            wxconfig.put("timestamp", ret.get("timestamp"));
            wxconfig.put("nonceStr", ret.get("nonceStr"));
            wxconfig.put("signature", ret.get("signature"));
            model.addAttribute("wxconfig", wxconfig);

            log.info("wxconfig ="+wxconfig.toString());

        } catch (Exception e) {
            log.error(e.toString());
        }

        return "course/index";
    }

    //發(fā)送請(qǐng)求峦椰,根據(jù)code獲取openId
    private String getOpenId(HttpServletRequest request,String code) {

        String content = "";
        String openId = "";
        //封裝獲取openId的微信API
        StringBuffer url = new StringBuffer();
        url.append("https://api.weixin.qq.com/sns/oauth2/access_token?appid=")
                .append(myWXPayConfig.getAppID())
                .append("&secret=")
                .append(myWXPayConfig.getAppsecret())
                .append("&code=")
                .append(code)
                .append("&grant_type=authorization_code");

        try {
            content = restTemplate.getForEntity(url.toString(), String.class).getBody();

            JSONObject json = JSONObject.parseObject(content);
            openId = json.getString("openid");

        } catch (Exception e) {
            log.error("http獲取openId請(qǐng)求失敗:", e);
        }
        return openId;
    }
}
2.發(fā)送模板消息

以發(fā)送公眾號(hào)模板消息為例汰规,展示openid的使用汤功。

第一步 獲取accessToken

@Service
@Slf4j
public class WxAccessTokenServiceImpl implements IWxAccessTokerService {

    private static AccessToken accessToken = null;

    @Resource
    private MyWXPayConfig wxPayConfig;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 獲取access_token的接口地址
     */
    public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * 通過(guò)APPID 和 APPSECRET
     * 獲取assess_token
     * @return
     */
    @Override
    public AccessToken getAccessToken() throws BusinessException {

        String appid = wxPayConfig.getAppID();
        String appsecret = wxPayConfig.getAppsecret();

        if(accessToken == null
                || DateUtils.addSeconds(accessToken.getReceiveTime(), accessToken.getExpires_in()).before(new Date())) {
            String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
            JSONObject jsonObject = restTemplate.getForObject(requestUrl,JSONObject.class);
            // 如果請(qǐng)求成功
            if (null != jsonObject && jsonObject.get("access_token")!=null) {
                try {
                    accessToken = new AccessToken();
                    accessToken.setAccess_token(jsonObject.getString("access_token"));
                    accessToken.setExpires_in(jsonObject.getInteger("expires_in"));
                    accessToken.setReceiveTime(new Date());
                    log.info("獲取token成功!");
                } catch (JSONException e) {
                    accessToken = null;
                    // 獲取token失敗
                    log.error("獲取token失敗 errcode:{} errmsg:{}", jsonObject.getInteger("errcode"), jsonObject.getString("errmsg"));
                }
            } else {
                throw new BusinessException(ResponseConstants.FAIL.getRetCode(), "獲取access_token失敗!");
            }
        } else {
            log.info("token在有效期內(nèi),直接返回!");
        }

        return accessToken;
    }
}

@Data
public class AccessToken {

    /**
     * 獲取到的憑證
     */
    private String access_token;

    /*
     * 憑證得到時(shí)間
     */
    private Date receiveTime;

    /**
     * 憑證有效時(shí)間,單位:秒
     */
    private int expires_in;

}

第二步 構(gòu)建模板參數(shù)實(shí)體類

/**
 * 微信模板類
 */
@Data
@ToString
public class WeChatTemplate implements Serializable {

    private static final long serialVersionUID = 612571563869874653L;
    /**
     * 模板id
     */
    private String template_id;

    /**
     * 接收者 openId
     */
    private String touser;

    /**
     * 模板跳轉(zhuǎn)鏈接
     */
    @JsonSerialize(include= JsonSerialize.Inclusion.NON_EMPTY)
    private String url;

    /**
     * data的數(shù)據(jù)
     */
    private TreeMap<String, TreeMap<String, String>> data;

    /**
     * data 里的數(shù)據(jù)
     *
     * @param value :模板參數(shù)
     * @param color :顏色 可選
     * @return
     */
    public static TreeMap<String, String> item(String value, String color) {
        TreeMap<String, String> params = new TreeMap<String, String>();
        params.put("value", value);
        params.put("color", color==null?"#173177":color);
        return params;
    }
}

第三步 模板消息發(fā)送服務(wù)

@Component
@Slf4j
public class WeChatTemplateMessageService {

    @Autowired
    private RestTemplate restTemplate;

    @Resource
    private WechatTemplateMessageLogMapper templateMessageLogMapper;

    /**發(fā)送模板消息*/
    public static final String SEND_TEMPLATE_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";

    /**
     * 發(fā)送模板消息
     * @param accessToken
     * @param template
     * @return
     */
    public void sendTemplateMsg(String accessToken, WeChatTemplate template){

        log.info("模板消息請(qǐng)求參數(shù)"+ template.toString());

        String requestUrl =SEND_TEMPLATE_MESSAGE.replace("ACCESS_TOKEN",accessToken);
        JSONObject jsonObject = restTemplate.postForObject(requestUrl,template,JSONObject.class);
        log.info("返回jsonObject值:"+jsonObject);
        if (null != jsonObject) {
            int errorCode = jsonObject.getIntValue("errcode");

            //發(fā)送日志
       
            if (0 == errorCode) {
                log.info("模板消息發(fā)送成功");
            } else {
                String errorMsg = jsonObject.getString("errmsg");
                log.info("模板消息發(fā)送失敗,錯(cuò)誤是 "+errorCode+",錯(cuò)誤信息是"+ errorMsg);
            }
        }
    }

}

最后 發(fā)送消息代碼

try {
            //發(fā)送消息
            AccessToken accessToken = wxAccessTokerService.getAccessToken();
            if(accessToken == null) {
                logger.error("accessToken為空溜哮,無(wú)法發(fā)送消息");
            } else {

                WechatTemplateMessageConfig config = templateMessageConfigMapper.selectByCode("APPLY");
                if(StringUtils.isBlank(config.getTemplate_id())) {
                    logger.error("模板id未配置滔金,無(wú)法發(fā)送消息");
                } else {
                    String first = "您已報(bào)名參加!";
                    allData.put("first", WeChatTemplate.item(first, null));
                    allData.put("keyword1",WeChatTemplate.item(TextFormatUtil.towDecimalPlacesFormat(courseApply.getTotal_fee())+"元",null));
                    allData.put("keyword2",WeChatTemplate.item(courseUser.getName(),null));
                    allData.put("keyword3",WeChatTemplate.item(courseUser.getMobile(),null));
                    allData.put("keyword4",WeChatTemplate.item(TextFormatUtil.timeFormatText(courseApply.getApply_time()),null));
                    allData.put("keyword5",WeChatTemplate.item("微信公眾號(hào)", null));
                    allData.put("remark",WeChatTemplate.item("感謝您的參與茂嗓!", null));
                    WeChatTemplate template = new WeChatTemplate();
                    template.setTouser(courseUser.getOpenid());
                    template.setTemplate_id(config.getTemplate_id());
                    template.setUrl(null);
                    template.setData(allData);
                    weChatTemplateMessageService.sendTemplateMsg(accessToken.getAccess_token(), template);
                }
            }

 }catch (Exception e){
   e.printStackTrace();
   logger.error("發(fā)送消息出現(xiàn)異常"+e.getMessage());
 }

至此 獲取openid及發(fā)送模板消息完成餐茵,后續(xù)如有時(shí)間會(huì)完成微信jssdk及微信支付內(nèi)容!如有發(fā)現(xiàn)錯(cuò)誤及不足還請(qǐng)幫忙指正述吸,謝謝忿族!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蝌矛,隨后出現(xiàn)的幾起案子道批,更是在濱河造成了極大的恐慌,老刑警劉巖入撒,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隆豹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡衅金,警方通過(guò)查閱死者的電腦和手機(jī)噪伊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氮唯,“玉大人鉴吹,你說(shuō)我怎么就攤上這事〕土穑” “怎么了豆励?”我有些...
    開(kāi)封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我良蒸,道長(zhǎng)技扼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任嫩痰,我火速辦了婚禮剿吻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘串纺。我一直安慰自己丽旅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布纺棺。 她就那樣靜靜地躺著榄笙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祷蝌。 梳的紋絲不亂的頭發(fā)上茅撞,一...
    開(kāi)封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音巨朦,去河邊找鬼米丘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛糊啡,可吹牛的內(nèi)容都是我干的蠕蚜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悔橄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腺毫?” 一聲冷哼從身側(cè)響起癣疟,我...
    開(kāi)封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎潮酒,沒(méi)想到半個(gè)月后睛挚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡急黎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年扎狱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勃教。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淤击,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出故源,到底是詐尸還是另有隱情污抬,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站印机,受9級(jí)特大地震影響矢腻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜射赛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一多柑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楣责,春花似錦竣灌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蛔屹,卻和暖如春削樊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兔毒。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工漫贞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人育叁。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓迅脐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親豪嗽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谴蔑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容