已经很久没有写文章了,此次借着做这个小项目的机会,重新update自己写代码的技能;自从转岗后比较少写代码,感觉都有点生疏了;好了,话不多话,进入本次正题;我先介绍下开发这个项目的背景,因为我们公司是初创企业,公司的项目基本上都是为了辅助企业管理及运营而开发;团队也是只有三五个人的小团队,我在这个团队主要负责产品管理及需求跟进。因为运营的原因,公司同时经营了几个品牌,每个品牌针对不同的市场,所以公司虽然不大,但公司的项目却多而杂。一直以来,系统更新都是整个团队最为头痛的事情,因为品牌的差异化,导致项目的维护成本大大增加。所以我跟项目组提了一个需求就是需要一个可以管理系统更新包及自动更新的系统。因为项目组同事还有一堆任务要做,这个需求提了几周了都还没有开始落地,最后决定自己先写一个简单的出来过渡(一开始想百度一个现成的,但一直没找到有合适,也有可能关键词没用对,哈哈);
目前公司的项目基本上都是使用PHP语言开写,近期新开的一个项目采用的是vue+php的形式,所以为了方便后续的维护及扩展,这个项目我还是选择了vue+php的组合。还有一个原因就是,之前我没接触过vue,想借这个机会了解下这个前端新宠;
最后确定下来项目框架如下:
前端:Vue 2.5.2+Element UI 2.12
后端:Codeigniter 3.1.10
数据库:Mysql 5.7.0
本地开发环境:PHP 7.0.5 + Apache 2.4
OK,进入正题;我给该项目取的名称为upgrade-web,由于upgrade-web采用前后端分离,整个项目自己在搞,所以我先从后端开始搞起;
首先下载最新稳定版的CI框架,CI框架是目前PHP框架中相对干净的一个,除了框架必要的功能,什么都没有提供了。选择这个框架的原因也在于自己想重温下以前写代码的逻辑。从零开始搭项目。
CI框架的应用目录结构如下:(只罗列了我们在项目中会使用到的相关文件,框架的核心目录及文件用途,有需要的朋友自行去官网查看,本文不做过多叙述)
upgrade-idcims
|-----application 应用目录
|-----core 重写框架的核心类
|-----helpers 重写框架的辅助函数
|-----libraries 自定义类库
|-----language 语言包
|-----config 应用配置
|-----config.php 应用相关的配置
|-----database.php 数据库相关的配置
|-----autoload.php 设置自动加载类库的配置
|-----constants.php 常量配置文件
|-----routes.php 路由配置文件
|-----controllers 控制器目录
|-----models 模型目录
|-----views 视图目录
|-----cache 存放数据或模板的缓存文件
|-----errors 错误提示模板
|-----hooks 钩子,在不修改系统核心文件的基础上扩展系统功能
|-----third_party 第三方库
|-----logs 日志
|-----system 框架程序目录
|-----index.php 入口文件
在写代码之前,我们先设计下表结构;upgrade-web的需求很简单,逻辑也不复杂,所以我们需要的表结构也不复杂;按前期能预计到的需求我们先设计三张表;用户表,更新包管理表,客户端管理表;
用户表:
CREATE TABLE `ui_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
`nickname` varchar(20) NOT NULL DEFAULT '' COMMENT '昵称',
`token` varchar(255) DEFAULT NULL COMMENT '登录token',
`activate_time` int(11) DEFAULT NULL COMMENT '活动时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `ui_user` (`id`, `username`, `password`, `nickname`, `token`, `activate_time`)
VALUES
(1, 'tim', 'bmB/xRUAs8t9gz1C6/gt/iZoRreGyK+Y9JXRWVr8YSLlZk5doX2mzFOeiEwKgDQWYae033Fk3betlHk9D5tVqM5HRh+GanzWz7DemvynSzyPw3wtA6IznqYF+t740j8+SHyFspKlqfYydHpwL0yh0Fy731fbXWSbtGdn2EhxI+Q=', '', '90b460c429255c2f80c9dedbdcb9ea158c37d16d', 1573612152);
更新包管理表
CREATE TABLE `ui_upgradepackage` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`package_path` text COMMENT '更新包路径',
`upgrade_description` text COMMENT '更新说明',
`can_upgrade` tinyint(1) DEFAULT '1' COMMENT '允许更新',
`note` text COMMENT '备注',
`version` double DEFAULT NULL COMMENT '版本号',
`git_version` varchar(255) DEFAULT NULL COMMENT 'git 版本号',
`upload_time` int(11) DEFAULT NULL COMMENT '上传时间',
`filename` varchar(255) DEFAULT NULL COMMENT '文件名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
客户端管理
CREATE TABLE `ui_clientmanager` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`client_name` varchar(255) DEFAULT NULL COMMENT '客户端名称',
`client_url` varchar(255) DEFAULT NULL COMMENT '客户端地址',
`current_version` double DEFAULT NULL COMMENT '当前版本',
`pre_version` double DEFAULT NULL COMMENT '上一个版本',
`auth_time` bigint(20) DEFAULT NULL COMMENT '授权时间',
`expire_time` bigint(20) DEFAULT NULL COMMENT '到期时间',
`lastupdate_time` bigint(20) DEFAULT NULL COMMENT '最后更新时间',
`update_status` varchar(50) DEFAULT 'unknow' COMMENT '更新状态(更新成功/正在更新/更新失败)',
`allow_server` varchar(50) DEFAULT NULL COMMENT '可管理服务器数量',
`allow_line` varchar(50) DEFAULT NULL COMMENT '可管理线路数量',
`note` text COMMENT '备注',
`last_version` double DEFAULT NULL COMMENT '最新版本',
`client_ip` varchar(25) DEFAULT NULL COMMENT '服务器IP',
`auth_code` varchar(255) DEFAULT NULL COMMENT '授权code',
`init_auth` tinyint(1) DEFAULT '2' COMMENT '是否初始授权 1=是2=否',
`auth_code_shadow` varchar(255) DEFAULT NULL COMMENT '授权code的影子',
`download_code` varchar(255) DEFAULT NULL COMMENT '下载码',
`download_code_time` int(11) DEFAULT NULL COMMENT '下载码超时时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
好了,框架有了,表结构也有了,现在可以开始码代码了。因为是自己一个人开发,并且需求相对比较明确,故直接省去了画功能流程的步骤,直接开干了,哈哈。
做为一个标准的后台管理系统,登录功能是一定要的吧,来让我们先从一个登录功能开始吧。
哦,在些之前还有一个事要先做,CI框架作为upgrade-web的后端,为前端提供数据接口,那我们就需要做好一些接口约定规范。统一接口数据返回格式;
我们在upgrade-web ----> libraries 目录下创建一个IResponse.php文件,代码如下:
$result, 'data'=>$data, 'type'=>$type, 'message'=>$message];
return $response;
}
/**
* [responseJson 返回json数据格式]
* @Author Tim
* @param array $data [业务数据]
* @param string $result [执行结果 success/faild]
* @param string $type [数据类型 data/info/redirect]
* @param string $message [提示信息]
* @return string
*/
public static function responseJson($data=[], $result='success', $type='data', $message='') {
echo json_encode(self::returnJson($data, $result, $type, $message));
}
}
既然有登录功能,那自然需要有一个控制类判断用户是否登录啦。所以我们创建一个权限控制类来帮我们完成一些会话及系统权限相关的操作(这个类后面再扩展其它权限认证,目前只做会话管理及判定)。同样在 upgrade-web ----> libraries 中创建一个Auth.php 文件,代码如下
_ci = & get_instance(); // 获取CI句柄
$this->_ci->load->library('session');
$this->_ci->load->model('M_user','m_user');
$this->authorization = $this->_ci->input->get_request_header('Authorization');
}
/**
* [isLogin 判断是否已登录]
* @Author Tim
* @return bool
*/
public function isLogin()
{
// todo: 加入用户是否已登录检测
$authorization = $this->_ci->session->userdata('authorization');
if(empty($authorization)) {
$this->_islogin = false;
} else {
// 判断token 是否一致,一个会话只允许一个token存在,新token替换旧token
if(isset($authorization['token']) && $this->authorization == $authorization['token']) {
$this->_user = $authorization['user'];
if(isset($this->_user['id']) && !empty($this->_user['id'])) {
if((time() - $this->_user['updatetokentime']) > $this->_updatetokentime) {
$this->_islogin = false;
$user = $this->_ci->m_user->findById($this->_user['id']); // 从数据库获取用户数据
if(isset($user['token']) && $user['token'] == $this->_user['token']) {
$token = sha1(time().rand());
// 更新token
if($this->_ci->m_user->update($user['id'], ['token'=>$token])) {
$this->_user['token'] = $token;
$this->_user['updatetokentime'] = time();
$this->updateUserSession($token);
$this->_islogin = true;
}
}
} else {
// token在有效期内
$this->_islogin = true;
}
}
} else {
// 清除当前会话token
$this->_ci->session->unset_userdata('Authorization');
$this->_islogin = false;
}
}
return $this->_islogin;
}
/**
* [login 登录]
* @Author Tim
* @param array $user [用户信息]
* @return bool
*/
public function login($user)
{
$this->_user = $user;
$this->_user['activate_time'] = time();
$this->_user['updatetokentime'] = time();
$this->_user['token'] = sha1(time().rand()); // 更新token
// 更新用户登录信息
if($this->_ci->m_user->update($this->_user['id'], ['activate_time'=>$this->_user['activate_time'],'token'=>$this->_user['token']]))
{
$this->updateUserSession($this->_user['token']);
return true;
}
return false;
}
/**
* [logout 登出]
* @Author Tim
* @return void
*/
public function logout()
{
$this->_ci->session->sess_destroy();
return $this->_ci->m_user->update($this->_user['id'], ['activate_time' => time(),'token'=>'']); // 更新退出时间
}
/**
* [getXToken 获取当前登录token]
* @Author Tim
* @return string
*/
public function getXToken()
{
return $this->authorization;
}
/**
* [getUser 获取当前用户登录信息]
* @Author Tim
* @return array
*/
public function getUser()
{
$authorization = $this->_ci->session->userdata('authorization');
return $authorization['user'];
}
/**
* [updateUserSession 更新用户会话信息]
* @Author Tim
* @param string token 登录token
* @return void
*/
private function updateUserSession($token)
{
$this->authorization = $token;
header('Authorization: '.$token);
$session_data = ['authorization' => ['token'=>$token, 'user'=>$this->_user]];
$this->_ci->session->set_userdata($session_data);
}
}
现在我们可以写登录的逻辑代码了。为了尽快完成该项目,所有非业务逻辑的代码都简单处理化,后面再抽时间完善。
在 upgrade-web ----> controllers 中创建Login.php
__needlogin__ = false;
parent::__construct();
$this->load->model('M_user',"m_user"); // 加载用户模型
}
public function index()
{
if($this->auth->isLogin()) {
// 已登录
IResponse::responseJson([], 'success', 'info', $this->lang->line('logined'));
} else {
// 未登录
$username = trim($this->input->post('username'));
$password = trim($this->input->post('password'));
$password = $this->rsa->privEncrypt($password);
log_message('error', $password);
$muser = $this->m_user->verifyUser($username, $password);
if(empty($muser)) {
sleep(1);
IResponse::responseJson([], 'faild', 'info', $this->lang->line('incorrect_account'));
return;
}
if($this->auth->login($muser)) {
IResponse::responseJson([], 'success', 'info', $this->lang->line('login_success'));
} else {
IResponse::responseJson([], 'faild', 'info', $this->lang->line('login_faild'));
}
}
}
/**
* [logout 登出]
* @Author Tim
* @return void
*/
public function logout()
{
if($this->auth->logout()) {
IResponse::responseJson([], 'success', 'info', $this->lang->line('logout_success'));
} else {
IResponse::responseJson([], 'faild', 'info', $this->lang->line('logout_faild'));
}
}
}
登录控制器中只包含了两个方法,登入与登出;结合权限控制类,我们可以使用postman工具测试登录功能。
今天先更新到这里,明天继续;