微信公众号:bugstack虫洞栈
沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例[Ing]等。
这个!截至到19年11月我已经工作6年了,从业Java但也折腾过C#、搞PHP也弄过中继器、IO板卡,似乎我就是一个很喜欢在技术上折腾的人!与此同时,我也搞了6年的个人小网站,它们的呈现形式多种多样;有用PHP自己捣鼓的技术站用于分享资料、书籍、软件等、有用PHPWIND和DISCUZ的搭建的个人论坛、有用emlog和wordpress搭建的个人博客、也有借用于github+hexo/jekyll的能力组装出的技术博客。但无一例外它们都战死于征战的路上了,亡于;org域名不能备案、PHP服务器瘫痪被清空、http连接被注入恶意内容、定位不准确经常换模式、缺少核心优质内容等等。但!老衲的心依然如春(cun),因为喜欢干一件事,往往来自于干一了件喜欢的事!
所以!从19年开始我又继续写博客了,注册了新的域名bugstack.cn,备了案、买了服务器、还喊了新的口号;沉淀、分享、成长,让自己和他人都能有所收获!并且将尘封已久的微信公众号找回;结构上调整、内容上布局、粉丝上求关注。在这期间遇到了更大的牛;小灰、王二哥、纯洁的微笑还有松哥等一群伙伴,从他们那学到很多知识,真的非常感谢!
那么!这次的产品功能总结一句话就是;将基于Github+Jekyll搭建的静态博客与我并未开发过的微信公众号功能打通,通过在文章短口令码加锁引导用户到公众号内回复密码可解锁内容,以此来获得粉丝关注,当然如果取消关注了则文章继续锁定。
在多说一句,我理解的产品;其实是使用研发技术力搭建出可以用于承载接收用户在各种设备上所生产的行为数据的一种产品化服务。所以有些产品在做减法,同时也有为丰富的功能做加法,但究其一点我们其实都是在为接收有价值数据服务的。兴衰存亡,皆在核心数据沉淀与运作上!
为了使博客粉丝主动关注微信公众号,我们在用户初次浏览文章时增加权限验证,给每一个用户都生成一个唯一码引导用户在公众号内回复解锁文章,以此来与微信openid对应。当用户取消关注时则进行删除openid或标记状态,使得用户无法继续浏览文章。其实为了更好的体验,我参照了大牛的方式内容60%的区域可见,其余内容渐进遮挡,蒙胧胧的感觉还挺美。整体流程图如下;
为了实现本产品功能,我准备了;
前端主要负责针对发布时设置了look: need的文章,在用户浏览文章检查是否有权限查看全部内容,当用户没有权限时隐藏文章60%内容,并通过页面结尾提醒用户在公众号内回复口令解锁文章。
<article class="post container need lock" itemscope="" itemtype="http://schema.org/BlogPosting" style="height: 4400px;">
<div class="row">
<div class="col-md-9 markdown-body">
<h2 id="前言介绍">前言介绍h2>
<p>为什么会有路由层?因为在微服务架构设计中,往往并不会直接将服务暴漏给调用端,而是通过调用路由层进行业务隔离,以达到不同的业务调用对应的服务模块。p>
<p><strong>Spring Cloud Zuulstrong>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://bugstack.cn/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);
itstack-ark-wx & 领域驱动设计方式设计
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
itstack-ark-wx & 建表语句
CREATE TABLE
user_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接口层
WxPortalController.java & 接收微信公众号验签与行为信息通知
微信公众号都是通过服务提供方的一个接口的get/post请求来执行操作的{这样设计真sao但真香}
get接口主要是验证签名
post接口会收到;关注、取消关注、用户的回复信息
/**
* 微信公众号:bugstack虫洞栈
* 纯洁版博客:https://bugstack.cn
* 沉淀、分享、成长,让自己和他人都能有所收获!
* Create by 付政委 on @2019
*/
@RestController
@RequestMapping("/wx/portal/{appid}")
public class WxPortalController {
private Logger logger = LoggerFactory.getLogger(WxPortalController.class);
@Autowired
private WxValidateService wxValidateService;
@Autowired
private WxReceiveService wxReceiveService;
/**
* 处理微信服务器发来的get请求,进行签名的验证
*
* 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 "";
}
}
}
BlogController.java & 博客验权接口,用于判断文章是否解锁
/**
* 微信公众号:bugstack虫洞栈
* 纯洁版博客:https://bugstack.cn
* 沉淀、分享、成长,让自己和他人都能有所收获!
* Create by 付政委 on @2019
*/
@CrossOrigin("https://bugstack.cn")
@RestController
@RequestMapping("/api")
public class BlogController {
private Logger logger = LoggerFactory.getLogger(BlogController.class);
@Autowired
private 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应用层
UserLockAuthService.java & 定义后由领域层实现
public interface UserLockAuthService {
boolean checkAuth(String token);
}
domain领域层
logic/LogicFilter.java & 定义逻辑模型,impl中有对应的一组的实现类
public interface LogicFilter {
String filter(BehaviorMatter request);
}
logic/impl/SubscribeFilter.java & 其中一个实现,关注时行为处理
@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";
@Override
public String filter(BehaviorMatter request) {
return content;
}
}
engine/impl/MsgEngineHandle.java & 引擎路由调用
@Service("msgEngineHandle")
public class MsgEngineHandle extends EngineBase {
@Value("${wx.config.originalid:你的Err默认值}")
private String originalId;
@Override
public 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.java & 数据库实现
@Repository("userAuthGrantRepository")
public class UserAuthGrantRepository implements IUserAuthGrantRepository {
@Autowired
private UserAuthDao userAuthDao;
@Override
public void grantAuth(String openId, String token) {
UserAuth userAuthReq = new UserAuth();
userAuthReq.setOpenId(openId);
userAuthReq.setToken(token);
userAuthReq.setUuid(openId + "_" + token);
userAuthDao.insert(userAuthReq);
}
@Override
public void revokeAuth(String openId) {
userAuthDao.delete(openId);
}
}
springboot配置打包部署成可运行war包
pom.xml
<packaging>warpackaging>
WxApplication.java
@SpringBootApplication
public class WxApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(WxApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(WxApplication.class, args);
}
}
修改配置application.yml,数据库配置、微信配置
server:
port: 80
spring:
# datasource:
# username: root
# password: 123456
# url: jdbc:mysql://localhost:3306/itstack-ark-wx?useUnicode=true&characterEncoding=utf8
# driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:/mybatis/mapper/*.xml
config-location: classpath:/mybatis/config/mybatis-config.xml
# 微信公众号配置信息
# originalid:原始ID
# appid:个人AppID
# token:开通接口服务自定义设置
wx:
config:
originalid: xxxxxxx
appid: xxxxxxx
token: xxxxxxx
[root@instance-39394m67 bin]# ./startup.sh
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.2.RELEASE)
2019-11-23 18:10:57.131 INFO 22052 --- [ost-startStop-1] org.itstack.ark.wx.WxApplication : Starting WxApplication v1.0.0-SNAPSHOT on instance-39394m67 with PID 22052 (/usr/local/java/apache-tomcat-8.5.37/webapps/itstack-ark-wx/WEB-INF/classes started by root in /usr/local/java/apache-tomcat-8.5.37/bin)
2019-11-23 18:10:57.158 INFO 22052 --- [ost-startStop-1] org.itstack.ark.wx.WxApplication : No active profile set, falling back to default profiles: default
2019-11-23 18:10:59.137 INFO 22052 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1890 ms
2019-11-23 18:11:01.050 INFO 22052 --- [ost-startStop-1] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-11-23 18:11:01.336 WARN 22052 --- [ost-startStop-1] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2019-11-23 18:11:01.677 INFO 22052 --- [ost-startStop-1] org.itstack.ark.wx.WxApplication : Started WxApplication in 5.873 seconds (JVM running for 10.311)
23-Nov-2019 18:11:01.718 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/java/apache-tomcat-8.5.37/webapps/itstack-ark-wx.war] has finished in [9,226] ms
23-Nov-2019 18:11:01.720 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/java/apache-tomcat-8.5.37/webapps/ROOT]
23-Nov-2019 18:11:01.746 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/java/apache-tomcat-8.5.37/webapps/ROOT] has finished in [26] ms
23-Nov-2019 18:11:01.753 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-80"]
23-Nov-2019 18:11:01.764 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
23-Nov-2019 18:11:01.766 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 9326 ms
2019-11-23 18:11:13.039 INFO 22052 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-11-23 18:11:13.059 INFO 22052 --- [p-nio-80-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 20 ms
2019-11-23 18:11:13.172 INFO 22052 --- [p-nio-80-exec-1] o.i.ark.wx.interfaces.BlogController : 校验博客浏览用户授权状态UDHIUS开始
验证连接;https://bugstack.cn/itstack-demo-ddd/2019/10/16/DDD%E4%B8%93%E9%A2%98%E6%A1%88%E4%BE%8B%E4%BA%8C-%E9%A2%86%E5%9F%9F%E5%B1%82%E5%86%B3%E7%AD%96%E8%A7%84%E5%88%99%E6%A0%91%E6%9C%8D%E5%8A%A1%E8%AE%BE%E8%AE%A1.html