产品研发记录01:为什么需要基础开发框架产品

  目前负责一套基础开发框架产品的研发,从0到1开始已历经两年时间,日趋稳定。希望通过本系列文章记录下产品研发过程中的方方面面,归纳总结沉淀下来。

一、背景

  传统行业,以项目为单位。在没有产品之前,所属部门所有人员打散进入一个个的项目,根据客户需求进行开发。Java体系,这个时候,各个项目基本上由各项目的技术负责人搭建项目开发环境,架构设计,模块拆分。完事后,再安排开发人员根据需求开发。之后,测试上线,最终验收,完成整个项目开发流程。之后再辗转新的项目,或者继续做该项目的下一期项目。

  这样的项目开发方式会带来如下问题:

  • 大量冗余工作。比如开发环境搭建,登录、首页、菜单、权限、人员管理、系统监控等公共需求基本上在每个项目中都得重新开发一遍。

  • 技术债务风险。主要技术的选型往往由各个项目的技术负责人确定,一些零散的组件,比如前端常用的图表、树、弹出框等基础组件往往由实际开发人员决定。带来所用技术混乱,后期运维成本提高。

  • 对开发人员要求提高。项目与项目之间技术风格迥异,开发人员转项目往往得重新学习了解一套新的技术体系。从一个项目的熟手变为另一项目的生手。

  • 技术、经验无法沉淀。对于传统行业,技术与行业实施经验才是无价之宝。由于各项目单打独斗,项目与项目间经验无法复用,知识很难沉淀。

二、基础开发框架产品

  为了解决上述问题,引入基础开发框架产品。所有项目的实施都以该基础框架产品为基座进行开发。该基座包含如下几点:

  • 整体架构前后端分离,使用RESTFULL进行前后端数据交互
  • 前后端技术体系统一
  • 由基础开发框架组负责技术选型,确定技术路线。比如React、Vue、Angular到表格组件,图表组件到底用哪个之类问题
  • 应用部分:提供登录、首页、菜单、人员管理、权限管理、字典管理、系统监控等通用模块
  • 前端部分:统一技术栈。提供通用前端开发组件,开发结构规范,提供公共的前端压缩合并以及缓存解决方案。
  • 安全:统一的XSS、CSRF、SQL注入等安全问题解决方案
  • 统一的框架级异常处理
  • 可平滑升级
  • 提供问题反馈、建议提交平台,构建正循环生态环境
  • ……

  总之,属于项目开发公共的部分都放入其中,随着项目的不断实施,不断的总结出新的组件,不断的完善现有基础组件。

三、带来的好处

  • 开发实施效率明显提升
  • 项目后期维护成本降低
  • 技术路线统一后项目开发人员的技术学习成本降低
  • 基于同一套基础开发框架,A项目中的闪光点可直接搬到B项目中
  • 由基础开发框架组造完轮子后避免各个项目重复造轮子
  • 形成公司核心竞争力

下一篇:产品研发记录02:基础开发框架产品的设计哲学

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

人生中唯一的2016

  在人生中的每一天都是唯一的,转眼就到了今年的最后一天。在这最后一天里,不禁想问自己,你在这一年里收获了什么?在时间长河里为这个世界留下了什么?创造了什么价值?在这一年里有哪些遗憾?接下来的一年里你有什么打算?

收获的

  • 拿到了驾照,从三月份报考,差不多半年,每周末练车,在十月份的时候驾照终于到手。

  • 读完了:《皮囊》《动物庄园》《黄金时代》《白夜行》《嫌疑人X的献身》《解忧杂货铺》《精进》《巨流河》《HTTP权威指南》《Head First设计模式》,正在读《禅与摩托车维修艺术》

  • 每天上班地铁背英语单词,坚持200多天,对英语有了一些感想

  • 练字,硬笔楷书,100天出头。

  • 500多个随手小视频,用视频记录下2016生活中的点滴。几十年后再来看这些视频,肯定会有一番不一样的感受。

  • 看了一段时间关于编译原理的视频与资料,对编译原理有了一些了解

留下的

  • 作为公司基础开发框架产品负责人,产品今年迭代了12个版本,越来越稳定,在稳定中不断的打磨脱变。通过该产品,提升了开发人员的开发效率。

  • 博客开通,分享50多篇原创博客,希望能帮助到一些人。

  • stackoverflow从0到将近500分,算是常年在上面找到有用答案的反馈。

其它重要事件

  • 年初跟媳妇儿以及爸妈全家人到云南自驾游一圈

  • 买房,三十而立算是有了基础

  • 老婆怀孕,明年等着升级,得多赚奶粉钱了

遗憾

  • 书法练习未能连续坚持

  • 系统架构师考试未能通过

2017

  • 博客继续写下去

  • 坚持练字

  • 囤着还未看的书,希望能看掉一些:《算法》第四版、《TCP/IP》卷一、《经济学原理》-曼昆、《瓦尔登湖》、《英语语法实践指南》、《数学之美》

  • 想要学习的领域:机器学习、推荐引擎

  • 接受新的挑战

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

MySQL 5.7 绿色版安装

安装环境为windows2008 R2,X64

一、下载解压

  官网下载地址:http://dev.mysql.com/downloads/mysql/
  选择跟环境对应的版本,然后解压即可。我这里解压到C:\work\mysql-5.7.17-winx64

二、 环境变量配置

  把C:\work\mysql-5.7.17-winx64\bin添加到用户的环境变量path

三、 添加配置文件

  复制my-default.ini一份该文件,在当前目录下粘贴为my.ini,并增加如下内容

1
2
3
4
5
6
7
8
[client]
default-character-set=utf8
[mysqld]
character_set_server=utf8
basedir=C:\work\mysql-5.7.17-winx64
datadir=C:\work\mysql-5.7.17-winx64\data
port = 3306

  修改默认的字符编码为UTF-8 解决中文乱码问题。

四、数据库初始化

  用管理员身份打开CMD,并执行如下命令:

  • mysqld --initialize --user=mysql --console

  结尾部分为初始化的随机root密码,可以先从控制台上复制下来,下一步修改密码会使用

五、修改root密码

  运行如下命令:

  • mysql -u root -p 之后会提示输入密码
  • set password for root@localhost = password('root');修改密码为root

六、创建新用户

  运行如下命令:

  • CREATE USER 'admin'@'%' IDENTIFIED BY 'password';
  • GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%';

  注意admin'@'%中的百分号,这里使用百分号意味着允许任意远程客户端连接。root@localhost意味着只允许本地客户端连接。

  官方文档:http://dev.mysql.com/doc/refman/5.7/en/adding-users.html

七、添加为系统服务

  运行如下命令:

  • mysqld --install MySQL57
  • net start MySQL57

  删除服务可运行sc delete MySQL57

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式总结

  看完《Head First设计模式》一书,本篇算是对设计模式做一个简单的总结。

一、为什么要使用设计模式

  个人觉得,设计模式存在的价值既是为了解决实际的问题。在碰到实际问题,合适的场景使用设计模式会带来如下好处:

  • 让代码结构更加清晰
  • 使得代码易于维护
  • 利于后期功能扩展
  • 使用共同的术语方便技术人员相互交流

二、如何使用设计模式

  对于如何使用设计模式,个人觉得没必要去记住书中的设计模式使用套路,一个是的确很难记,另外就是设计模式应当是在解决实际问题中自然而然生长出来的,而不是套出来的。 但是,还是应当对设计模式有所了解,这样在解决问题时容易获得灵感。

三、感想

  在看本书之前,只熟悉常见的几个设计模式。看完之后给我最大的感受就是,虽然很多模式不知道,但是在实际的工作当中已经使用了大量的设计模式,比如装饰者、观察者、策略等模式。
  当在实际工作当中,遇到需要解决的问题时,自然而然就会去想程序该怎么设计比较合理。在思考整个代码结构如何才能更优的过程中,应当忘记设计模式的使用套路,专注与问题的本质上。当以最优的方式解决问题后,这个最优解其实就是一种模式。
  最后,一切应当以解决问题为导向。切记为了使用设计模式而生搬硬套的使用设计模式。代码应在解决问题的基础上以简洁、优雅、易懂为好。

四、设计模式系列文章

设计模式系列之一单例模式
设计模式系列之二策略模式
设计模式系列之三观察者模式
设计模式系列之四装饰者模式
设计模式系列之五工厂模式
设计模式系列之六命令模式
设计模式系列之七适配器模式
设计模式系列之八外观模式
设计模式系列之九模板方法模式
设计模式系列之十迭代器模式
设计模式系列之十一组合模式
设计模式系列之十二状态模式
设计模式系列之十三代理模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

解决正确配置Servlet async-supported参数报错问题

一、描述

环境:

JDK 1.7
Servlet 3.0
tomcat 7

  Servlet 3.0新增异步处理支持,配置servlet参数<async-supported>true</async-supported> ,或通过注解方式启用@WebServlet(urlPatterns = "/demo",asyncSupported = true) 。当正确配置后,发现照例报如下错误:

1
2
3
4
java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.
org.apache.catalina.connector.Request.startAsync(Request.java:1660)
org.apache.catalina.connector.Request.startAsync(Request.java:1653)
org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1022)

二、解决办法

  找了一圈,所有的解决办法都在说可能是参数配置未配置正确,或者是需要在server.xml上配置。最后,在StackOverflow上找到了如下答案

1
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

三、其它

Servlet 3.0 新特性详解:https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之十三代理模式

  代理模式可以理解为通过一个中间者来访问数据,比如我们通过代理来访问互联网,通过代理商购买游戏点卡等。这些都属于代理模式的设计思想。

  上图为代理模式的一般数据交互逻辑。在Java后台开发中,使用RMI调用远程数据既采用了代理模式。

  在我的实际开发中,以个人开发经验,使用代理模式最主要是为了解决如下两个问题:

  1. 封装访问远程数据的具体实现逻辑。即客户端只需调用Proxy提供的方法即可,不关心数据从哪里取,本地or远程。
  2. 提供一个统一的口径实现过滤。

  下面分享一个使用到代理模式设计思想的代码样例。需求是这样的,前后端分离,前端统一使用$.ajax来请求后端提供的RESTFULL服务。需要在框架层面做到系统级的异常处理,即开发人员不需再关心系统级的异常,只需要关注业务逻辑。
  针对于该需求,明显是需要一个Proxy,通过一个Proxy来转发前后端的交互。或者说叫做数据请求过滤器的东西,来劫持所有的前端数据请求。最终的实现样例代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
(function(){

//ajax方法clone
var _ajax = $.ajax;

//这里相当于劫持了原生的$.ajax方法,通过劫持实现对请求的过滤,系统级异常的捕获
$.ajax = function(param) {
//ajax deferred promise 保持原有promise操作
var promise;

var _param = $.extend({}, {autoWrap : true}, param);

//如果不需要默认的封装:autoWrap = false;
if (!_param.autoWrap) {
promise = _ajax(_param);
//直接返回
return promise;
}

//default ajax options
var defaultOpts = {
type: "POST",
dataType: "json",
cache: false,
headers: {
"CSRFToken" : getCSRFToken()
}
};
//deep extend
_param = $.extend(true, defaultOpts, _param);

//add timestamp
if (!_param.cache) {
var _url = _param.url || "";
var stamp = "T="+ new Date().getTime();
(_url.indexOf("?") == -1) ?( _url += "?"+stamp):( _url += "&"+stamp);
_param.url = _url;
}

if (_param.success) {
_param.callback = _param.success;
delete _param.success;
}

promise = _ajax($.extend({
success:function(response){
var code = response.status || response.code || "200";
//如果是整形,转换为字符串
typeof(code)=="number" && (code=code+"");
//状态码控制
switch(code){
case "200":
//数据转发
if (_param.callback) {
_param.callback(response);
}
break;
case "401":
//需要认证:登录超时或未登录.Session超时,弹出登陆框,登录成功后继续业务逻辑。
ReLogin(function(){
$.ajax(param);
});
break;
case "400":
alert("请求异常!");
break;
case "403":
alert("请求的资源未授权!");
break;
case "500":
alert("<code>500:</code>服务器端异常,请联系管理员.<br/>"+(response.msg || response.message));
break;
default :
alert("<code>"+code+":</code>服务器端异常,请联系管理员.<br/>"+(response.msg || response.message));
}
},
error:function(XMLHttpRequest, textStatus, errorThrown){
console.log("ajax error:" + textStatus);
}
}, _param));

return promise;
};
})();

上一篇:设计模式系列之十二状态模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之十二状态模式

  举一个自动取款机的场景来说明状态模式的使用。去自动提款机取钱首先得有插卡的动作,最后还会有取卡的动作。对应两个状态,一个是无卡状态,另外一个是有卡状态。

一、代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ATM {
final static int NO_CARD = 0;
final static int HAVE_CARD = 1;
//提款机初始状态为无卡
int state = NO_CARD;

//插卡
public void insertCard(){
if(state==NO_CARD){
System.out.println("插入卡片");
//设置状态为有卡状态
setState(HAVE_CARD);
}else if(state==HAVE_CARD){
System.out.println("已插入银行卡");
}
}

//取卡
public void quitCard(){
if(state==NO_CARD){
System.out.println("你没有插入银行卡");
}else if(state==HAVE_CARD){
System.out.println("退出卡片");
//设置状态为无卡状态
setState(NO_CARD);
}
}

public void setState(int state){
this.state = state;
}
}

  从上面的代码可以预见到,如果再增加状态,那么对应的操作里面就需要增加一组if-else,越到后面代码将越来越难以维护。

二、使用状态模式改造

1
2
3
4
5
6
7
//状态接口
public interface State {
//插入卡片
public void insertCard();
//取出卡片
public void quitCard();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//无卡状态
public class NoCard implements State{
NewATM atm;
public NoCard(NewATM newATM){
this.atm = newATM;
}

@Override
public void insertCard() {
System.out.println("插入卡片");
atm.setState(NewATM.HAVE_CARD);
}

@Override
public void quitCard() {
System.out.println("你没有插入银行卡");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//有卡状态
public class HaveCard implements State{
NewATM atm;
public HaveCard(NewATM newATM){
this.atm = newATM;
}

@Override
public void insertCard() {
System.out.println("已插入银行卡");
}

@Override
public void quitCard() {
System.out.println("退出卡片");
atm.setState(NewATM.NO_CARD);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//使用状态模式改造后的业务代码
public class NewATM {
static State NO_CARD;
static State HAVE_CARD;

private State state = NO_CARD;

public NewATM(){
NO_CARD = new NoCard(this);
HAVE_CARD = new HaveCard(this);
}
//插卡
public void insertCard(){
state.insertCard();
}

//取卡
public void quitCard(){
state.quitCard();
}

public void setState(State state){
this.state = state;
}
}

  经过改造,彻底干掉了繁琐的if-else状态判断。当有业务变更,或是新状态加入时可灵活的调整代码。

系列文章:
设计模式系列之一单例模式
设计模式系列之二策略模式
设计模式系列之三观察者模式
设计模式系列之四装饰者模式
设计模式系列之五工厂模式
设计模式系列之六命令模式
设计模式系列之八外观模式
设计模式系列之七适配器模式
设计模式系列之九模板方法模式
设计模式系列之十迭代器模式
设计模式系列之十一组合模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Maven私服Nexus3.x权限配置

一、禁用匿名访问权限

二、角色创建

三、人员创建

四、代理配置

  通过该配置可以使得两个Nexus服务器相关联。

1.配置地址

2.配置用户

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

设计模式系列之十一组合模式

组合模式:允许将对象组合成树型结构来表现“整体/部分”层次结构。组合能让一致的方式处理个别对象以及对象组合。

  接上一篇**设计模式系列之十迭代器模式**的业务逻辑,现在集团公司业务扩张,新增了湖南分公司,那么采用迭代器模式可以很方便的打印出湖南分公司的所有部门。

一、新需求实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//湖南分公司
public class HNBranch {
ArrayList<Dept> depts;

public HNBranch(){
depts = new ArrayList<Dept>();
//添加部门
depts.add(new Dept("湖南-财务部"));
depts.add(new Dept("湖南-开发部"));
}

//这里不再使用自定义实现的Iterator接口,而是使用Java原生集合对象的Iterator
public Iterator createrIterator(){
return new HNBranchIterator(depts);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//湖南分公司的部门迭代器
public class HNBranchIterator implements Iterator{
ArrayList<Dept> depts;
int position = 0;
public HNBranchIterator(ArrayList<Dept> depts){
this.depts = depts;
}
@Override
public boolean hasNext() {
if(position>=depts.size() || depts.get(position)==null){
return false;
}else{
return true;
}
}

@Override
public Object next() {
Dept dept = depts.get(position);
position = position + 1;
return dept;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestNew {

public static void main(String[] args) {
Iterator BJ = new BJBranch().createrIterator();
Iterator SH = new SHBranch().createrIterator();
Iterator HN = new HNBranch().createrIterator();

printDeptName(BJ);
printDeptName(SH);
//打印新设立的湖南分公司的所有部门
printDeptName(HN);
}

private static void printDeptName(Iterator iterator){
while(iterator.hasNext()){
Dept dept = (Dept) iterator.next();
System.out.println(dept.getName());
}
}
}

  依次类推,如果再设立其它省份的分公司一样的可以很快增加上去。但是,如果需要在湖南分公司下再设立下隶属于湖南分公司的长沙分公司呢?此时,整个结构变为一棵树型结构。

二、组合模式使用

  可使用组合模式来表现“整体/部分”层次结构。

1
2
3
4
5
6
7
8
9
10
11
//不管是分子机构,还是分支机构下的部门对象都需要继承组件对象
public abstract class Component {
//不管是父节点还是叶子节点都有该方法
public abstract String getName();
public abstract void print();

//叶子节点可以没有该方法
public void add(Component component) {
throw new UnsupportedOperationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//分支机构,即父节点
public class Branch extends Component{
ArrayList<Component> components;
private String name;

public Branch(String name){
this.name = name;
components = new ArrayList<Component>();
}

public void add(Component component){
components.add(component);
}

public String getName(){
return this.name;
}

public void print(){
System.out.println(getName());
System.out.println("----------------------");
//打印子节点,这里既可以是叶子节点也可以是父节点。通过这里实现了递归
for(Component com:components){
com.print();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//机构中的部门对象,即叶子节点
public class Dept extends Component{
private String name;

public Dept(String name){
this.name= name;
}

public String getName(){
return this.name;
}

public void print(){
System.out.println(getName());
System.out.println("----------------------");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//测试代码
public class Test {
public static void main(String[] args) {
//下面的为同级
Component BJBranch = new Branch("北京分公司");
BJBranch.add(new Dept("北京-财务部"));
Component SHBranch = new Branch("上海分公司");
SHBranch.add(new Dept("上海-财务部"));
Component HNBranch = new Branch("湖南分公司");
HNBranch.add(new Dept("湖南-财务部"));
//下面的为子级
Component CS = new Branch("长沙子公司");
CS.add(new Dept("长沙-财务部"));
HNBranch.add(CS);

Component root = new Branch("集团公司");
root.add(BJBranch);
root.add(SHBranch);
root.add(HNBranch);
//这里会递归遍历下去,直至所有节点遍历完毕
root.print();
}
}

  从上面的代码中可以看出,核心在于父节点与叶子节点都属于Component,才能组合在一起,实现递归打印。

上一篇:设计模式系列之十迭代器模式
下一篇:设计模式系列之十二状态模式

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Linux下搭建Maven私服Nexus3.X

  操作系统为中标麒麟6,安装的Nexus版本为nexus-3.2.0-01-unix.tar.gz

一、准备

  1. 从官网 https://www.sonatype.com/download-oss-sonatype 下载最新版本的资源
  2. Nexus 3.x.x的JDK环境要求为1.8

二、安装配置

  下载下来nexus-3.2.0-01-unix.tar.gz后,我使用SecureFX把资源上传到了/opt目录下。SecureFX是一个客户端工具,通过UI界面就能对Linux上的文件做操作。

  接下来使用SecureCRT连接上Linux服务器,在命令行输入如下命令

1
2
3
4
5
6
7
8
9
10
# 切换到opt目录
$ cd /opt

# 解压tar文件
$ tar xvzf nexus-3.2.0-01-unix.tar.gz

# 先切换到bin目录
$ cd nexus-3.2.0-01/bin/
# 启动服务器,同时还有stop,restart
$ ./nexus start

  默认端口为8081,此时可通过http://yourIp:8081访问Nexus服务器。默认用户名与密码为admin/admin123.

  如果要修改默认端口,可以到安装目录下找到etc/nexus-default.properties,修改application-port项,然后restart即可。

  如果要卸载,使用rm -rf nexus-3.2.0-01删除掉安装目录即可。-r为子目录一起删,f为不用一一提示。

三、其它

  在安装完修改端口为8083后,在浏览器中输入地址发现无法访问。

  首先在Linux服务器上查看8083端口是否启用,通过netstat -lnp命令即可。

  确认端口已正在使用服务器没问题后,在客户端的CMD中通过telnet yourIp 8083,发现客户端无法连接。

  最后排查防火墙,在防火墙中设置允许8083端口的HTTP访问解决问题。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。