1. 前言介绍这个!截至到19年11月我已经工作6年了,从业Java但也折腾过C#、搞PHP也弄过中继器、IO板卡,似乎我就是一个很喜欢在技术上折腾的人!与此同时,我也搞了6年的个人小网站,它们的呈现形式多种多样;有用PHP自己捣鼓的技术站用于分享资料、书籍、软件等、有用PHPWIND和DISCUZ的搭建的个人论坛、有用emlog和wordpress搭建的个人博客、也有借用于github+hexo/jekyll的能力组装出的技术博客。但无一例外它们都战死于征战的路上了,亡于;org域名不能备案、PHP服务器瘫痪被清空、http连接被注入恶意内容、定位不准确经常换模式、缺少核心优质内容等等。但!老衲的心依然如春(cun),因为喜欢干一件事,往往来自于干一了件喜欢的事! 所以!从19年开始我又继续写博客了,注册了新的域名,备了案、买了服务器、还喊了新的口号;沉淀、分享、成长,让自己和他人都能有所收获!并且将尘封已久的微信公众号找回;结构上调整、内容上布局、粉丝上求关注。在这期间遇到了更大的牛;小灰、王二哥、纯洁的微笑还有松哥等一群伙伴,从他们那学到很多知识,真的非常感谢! 那么!这次的产品功能总结一句话就是;将基于Github+Jekyll搭建的静态博客与我并未开发过的微信公众号功能打通,通过在文章短口令码加锁引导用户到公众号内回复密码可解锁内容,以此来获得粉丝关注,当然如果取消关注了则文章继续锁定。 在多说一句,我理解的产品;其实是使用研发技术力搭建出可以用于承载接收用户在各种设备上所生产的行为数据的一种产品化服务。所以有些产品在做减法,同时也有为丰富的功能做加法,但究其一点我们其实都是在为接收有价值数据服务的。兴衰存亡,皆在核心数据沉淀与运作上! 2. 流程设计为了使博客粉丝主动关注微信公众号,我们在用户初次浏览文章时增加权限验证,给每一个用户都生成一个唯一码引导用户在公众号内回复解锁文章,以此来与微信openid对应。当用户取消关注时则进行删除openid或标记状态,使得用户无法继续浏览文章。其实为了更好的体验,我参照了大牛的方式内容60%的区域可见,其余内容渐进遮挡,蒙胧胧的感觉还挺美。整体流程图如下; 3. 功能实现为了实现本产品功能,我准备了;
3.1 前端前端主要负责针对发布时设置了look: need的文章,在用户浏览文章检查是否有权限查看全部内容,当用户没有权限时隐藏文章60%内容,并通过页面结尾提醒用户在公众号内回复口令解锁文章。
<article class="post container need lock" itemscope="" itemtype="http:///BlogPosting" style="height: 4400px;"><div class="row"><div class="col-md-9 markdown-body"><h2 id="前言介绍">前言介绍</h2><p>为什么会有路由层?因为在微服务架构设计中,往往并不会直接将服务暴漏给调用端,而是通过调用路由层进行业务隔离,以达到不同的业务调用对应的服务模块。</p><p><strong>Spring Cloud Zuul</strong></p>
// 文章所在容器的选择器var articleSelector = 'article.post.container.need';// 找到文章所在的容器var $article = $(articleSelector);// 文章的实际高度var article = $article[0], height = article.clientHeight;// 文章隐藏后的高度var halfHeight = height * 0.4;// 隐藏缩小$article.css('height', halfHeight + 'px');$article.addClass('lock');
.asb-post-01 { position: absolute; left: 0; bottom: 0; width: 100%; display: none; z-index: 10000;margin-bottom: 0;}.asb-post-01 .mask { height: 240px; width: 100%; background: -webkit-gradient(linear, 0 top, 0 bottom, from(rgba(255, 255, 255, 0)), to(#fff));}
UM_distinctid = 16e9cd64925334-0882eb883c9554-7711b3e-144000-16e9cd6492631c function getCookie(name) {var value = "; " + document.cookie;var parts = value.split("; " + name + "=");if (parts.length == 2)return parts.pop().split(";").shift();}function getToken() {let value = getCookie('UM_distinctid');if (!value) {return getUUID().toUpperCase();}return value.substring(value.length - 6).toUpperCase();}
// 查询后端的结果var _detect = function() {console.info(token);$.ajax({url : 'https:///xx/xx/check',type: "GET",dataType: "text",data : {token : token},success : function(data) {console.log(data);if (data == 'refuse') {_lock();} else {_unlock();}},error : function(data) {_unlock();}})}// 定时任务_detect();setInterval(function() {_detect();}, 5000);
3.2 后端
开发环境
工程代码
itstack-ark-wx └── src ├── main │ ├── java │ │ └── org.itstack.demo │ │ ├── application │ │ │├── UserLockAuthService.java │ │ │├── WxReceiveService.java │ │ │└── WxValidateService.java │ │ ├── domain │ │ │├── lockauth │ │ ││ ├── repository │ │ ││ │ └── IUserAuthPatrolRepository.java │ │ ││ └── service │ │ ││ └── UserLockAuthServiceImpl.java │ │ │├── receive │ │ ││ ├── model │ │ ││ │ ├── BehaviorMatter.java │ │ ││ │ └── MessageTextEntity.java │ │ ││ ├── repository │ │ ││ │ └── IUserAuthGrantRepository.java │ │ ││ └── service │ │ ││ ├── engine │ │ ││ │ ├── impl │ │ ││ │ │└── MsgEngineHandle.java │ │ ││ │ ├── Engine.java │ │ ││ │ ├── EngineBase.java │ │ ││ │ └── EngineConfig.java │ │ ││ ├── logic │ │ ││ │ ├── impl │ │ ││ │ │├── AnswerFilter.java │ │ ││ │ │├── SubscribeFilter.java │ │ ││ │ │├── UnlockFilter.java │ │ ││ │ │└── UnsubscribeFilter.java │ │ ││ │ └── LogicFilter.java │ │ ││ └── WxReceiveServiceImpl.java │ │ │└── validate │ │ │ └── service │ │ │ └── WxValidateServiceImpl.java │ │ ├── infrastructure │ │ │├── common │ │ ││ └── Constants.java │ │ │├── dao │ │ ││ └── UserAuthDao.java │ │ │├── po │ │ ││ └── UserAuth.java │ │ │├── repository │ │ ││ ├── UserAuthGrantRepository.java │ │ ││ └── UserAuthPatrolRepository.java │ │ │└── util │ │ │ ├── sdk │ │ │ │ └── SignatureUtil.java │ │ │ └── XmlUtil.java │ │ ├── interfaces │ │ │├── BlogController.java │ │ │└── WxPortalController.java │ │ └── WxApplication.java │ └── resources │ ├── mybatis │ └── application.yml └── test └── java └── org.itstack.ark.wx.test └── ApiTest.java
CREATE TABLEuser_auth(id bigint NOT NULL AUTO_INCREMENT,openId VARCHAR(64),token VARCHAR(16) NOT NULL,uuid VARCHAR(128),createTime DATETIME,updateTime DATETIME,PRIMARY KEY (id, token),CONSTRAINT idx_uuid UNIQUE (uuid))ENGINE=InnoDB DEFAULT CHARSET=utf8 讲解部分重点代码块,完整代码下载关注公众号;bugstack虫洞栈 & 回复:itstack-ark-wx interfaces接口层
/** * 微信公众号:bugstack虫洞栈 * 纯洁版博客:https:// * 沉淀、分享、成长,让自己和他人都能有所收获! * Create by 付政委 on @2019 */@RestController@RequestMapping("/wx/portal/{appid}")public class WxPortalController {private Logger logger = LoggerFactory.getLogger(WxPortalController.class);@Autowiredprivate WxValidateService wxValidateService;@Autowiredprivate WxReceiveService wxReceiveService;/** * 处理微信服务器发来的get请求,进行签名的验证 * <p> * appid 微信端AppID * signature 微信端发来的签名 * timestamp 微信端发来的时间戳 * nonce 微信端发来的随机字符串 * echostr 微信端发来的验证字符串 */@GetMapping(produces = "text/plain;charset=utf-8")public String validate(@PathVariable String appid, @RequestParam(value = "signature", required = false) String signature, @RequestParam(value = "timestamp", required = false) String timestamp, @RequestParam(value = "nonce", required = false) String nonce, @RequestParam(value = "echostr", required = false) String echostr) {try {logger.info("微信公众号验签信息{}开始 [{}, {}, {}, {}]", appid, signature, timestamp, nonce, echostr);if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {throw new IllegalArgumentException("请求参数非法,请核实!");}boolean check = wxValidateService.checkSign(signature, timestamp, nonce);logger.info("微信公众号验签信息{}完成 check:{}", appid, check);if (!check) return null;return echostr;} catch (Exception e) {logger.error("微信公众号验签信息{}失败 [{}, {}, {}, {}]", appid, signature, timestamp, nonce, echostr, e);return null;}}/** * 此处是处理微信服务器的消息转发的 */@PostMapping(produces = "application/xml; charset=UTF-8")public String post(@PathVariable String appid, @RequestBody String requestBody, @RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("openid") String openid, @RequestParam(name = "encrypt_type", required = false) String encType, @RequestParam(name = "msg_signature", required = false) String msgSignature) {try {logger.info("接收微信公众号信息请求{}开始 {}", openid, requestBody);MessageTextEntity message = XmlUtil.xmlToBean(requestBody, MessageTextEntity.class);BehaviorMatter behaviorMatter = new BehaviorMatter();behaviorMatter.setOpenId(openid);behaviorMatter.setFromUserName(message.getFromUserName());behaviorMatter.setMsgType(message.getMsgType());behaviorMatter.setContent(message.getContent());behaviorMatter.setEvent(message.getEvent());behaviorMatter.setCreateTime(new Date(Long.parseLong(message.getCreateTime()) * 1000L));// 处理消息String result = wxReceiveService.doReceive(behaviorMatter);logger.info("接收微信公众号信息请求{}完成 {}", openid, result);return result;} catch (Exception e) {logger.error("接收微信公众号信息请求{}失败 {}", openid, requestBody, e);return "";}}}
/** * 微信公众号:bugstack虫洞栈 * 纯洁版博客:https:// * 沉淀、分享、成长,让自己和他人都能有所收获! * Create by 付政委 on @2019 */@CrossOrigin("https://")@RestController@RequestMapping("/api")public class BlogController {private Logger logger = LoggerFactory.getLogger(BlogController.class);@Autowiredprivate UserLockAuthService userLockAuthService;@GetMapping(value = "check", produces = "application/json;charset=utf-8")public String check(@RequestParam String token) {try {logger.info("校验博客浏览用户授权状态{}开始", token);boolean status = userLockAuthService.checkAuth(token);logger.info("校验博客浏览用户授权状态{}完成", token, status);return status ? "success" : "refuse";} catch (Exception e) {logger.error("校验博客浏览用户授权状态{}失败", token, e);return "refuse";}}} application应用层
public interface UserLockAuthService {boolean checkAuth(String token);} domain领域层
public interface LogicFilter {String filter(BehaviorMatter request);}
@Service("subscribe")public class SubscribeFilter implements LogicFilter {private final String content = "您好!\n" +"\n" +"非常感谢您关注,微信公众号:bugstack虫洞栈 | 也期待您分享给更多小伙伴!\n" +"\n" +"bugstack虫洞栈,专注于原创技术专题案例,以最易学习编程开发的方式分享技术知识,让萌新、小白、大牛都能有所收获。目前已经完成的专题有;《Netty4.x从入门到实战》、《手写RPC框架》、《用Java实现JVM》、《基于JavaAgent的全链路监控》、《DDD专题案例》,其他更多专题还在排兵布阵中。\n" +"\n" +"获取专题案例源码回复;netty案例、rpc案例、用Java实现jvm源码、基于JavaAgent的全链路监控案例、DDD落地。\n" +"\n" +"联系作者:付政委 | monkeycode";@Overridepublic String filter(BehaviorMatter request) {return content;}}
@Service("msgEngineHandle")public class MsgEngineHandle extends EngineBase {@Value("${wx.config.originalid:你的Err默认值}")private String originalId;@Overridepublic String process(BehaviorMatter request) throws Exception {LogicFilter router = super.router(request);if (null == router) return null;String resultStr = router.filter(request);if (StringUtils.isBlank(resultStr)) return "";//反馈信息[文本],暂时只有文本后续按需拓展MessageTextEntity res = new MessageTextEntity();res.setToUserName(request.getOpenId());res.setFromUserName(originalId);res.setCreateTime(String.valueOf(System.currentTimeMillis() / 1000L));res.setMsgType("text");res.setContent(resultStr);return XmlUtil.beanToXml(res);}} infrastructure基础层
@Repository("userAuthGrantRepository")public class UserAuthGrantRepository implements IUserAuthGrantRepository {@Autowiredprivate UserAuthDao userAuthDao;@Overridepublic void grantAuth(String openId, String token) {UserAuth userAuthReq = new UserAuth();userAuthReq.setOpenId(openId);userAuthReq.setToken(token);userAuthReq.setUuid(openId + "_" + token);userAuthDao.insert(userAuthReq);}@Overridepublic void revokeAuth(String openId) {userAuthDao.delete(openId);}} 3.3 部署1. 工程打包
2. 服务上线
3. 功能验证
4. 综上总结
|
|