46 KiB
Ciyon后台管理系统 - PHP开发框架 Skill文档
系统概述
Ciyon后台管理系统是一个基于PHP8的SaaS平台后台管理框架,采用现代化的Web技术栈,提供完整的用户管理、权限控制、数据管理等核心功能。
技术特点
- PHP命名空间:采用现代化的命名空间组织代码
- MVC架构:控制器与视图分离,便于维护和扩展
- API:统一POST AJAX接口调用,返回JSON格式数据
- 安全机制:完整的用户认证、权限控制、数据加密
- 数据库抽象:基于PDO的数据库操作层,支持事务处理
- 模块化设计:可插拔的功能模块,便于功能扩展
目录结构
/web/
├── ambap/ # 某个移动端的API目录
├── admin/ # 后台管理主目录
│ ├── common.php # 后台管理公共函数库
│ ├── common.js # 前端公共JavaScript
│ ├── index.php # 后台管理主控制器
│ ├── index.html # 后台管理主页面
│ ├── login.php # 登录控制器
│ ├── login.html # 登录页面
│ ├── welcome.php # 欢迎页控制器
│ ├── welcome.html # 欢迎页面
│ ├── upload.php # 文件上传处理
│ ├── ap/ # 平台用户中心
│ │ ├── user.html # 平台用户管理前端
│ │ ├── user.php # 平台用户管理后端
│ │ └── ... # 其他平台功能模块
│ ├── saas1/ # SaaS租户管理,可能有多种机构角色,例如卖家/买家/代理等
│ │ ├── content.html # 内容管理前端
│ │ ├── content.php # 内容管理后端
│ │ └── ... # 其他模块
│ ├── autotask/ # 定时任务目录
│ │ ├── task.php # crond系统调用入口控制器
│ │ ├── run.php # 手动运行入口
│ │ ├── base.php # 定时任务函数库
│ │ └── ... # 其他定时任务函数库
│ └── [其他功能目录] # 如:cemap、datasse等
├── jscss/ # 前端资源
│ ├── ciy.js # 核心JS库
│ ├── ciycmp.js # 组件库
│ ├── ciycmp2.js # 组件库扩展
│ ├── ciytable.js # 列表组件库(表格、卡片)
│ └── style.css # 样式文件
└── cwebcomon.php # 公共函数定义类库
/zciyphp/ # PHP核心类库
├── comm.php # 通用公共函数库
├── db.php # 数据库操作类
├── sql.php # SQL构建类
├── post.php # POST参数处理类
├── web.php # Web工具类
├── html.php # HTML处理类
├── http.php # HTTP工具类
├── upload.php # 文件上传类
├── excel.php # Excel处理类
├── openai.php # AI接口类
├── smtp.php # 邮件发送类
└── ... # 其他工具类
核心架构
命名空间组织
系统采用命名空间进行代码组织:
web\admin- 后台管理控制器命名空间web\cwebcomon- 公共函数定义类命名空间ciy- 核心工具类命名空间
控制器结构
控制器类采用静态方法模式,每个功能对应一个静态方法:
namespace web\admin;
class index {
// 初始化页面数据
public static function json_init() { }
// 添加收藏菜单
public static function json_favadd() { }
// 删除收藏菜单
public static function json_favdel() { }
}
路由机制
系统通过路由自动调用对应的控制器方法:
- API URL格式:
/admin/index.init - 系统自动调用
web\admin\index::json_init() - 所有AJAX接口方法名以
json_开头
响应格式
统一使用JSON格式响应:
// 成功响应
$ret['data'] = array();
return succjson($ret);
// 返回:{"code":1, "data":{...}}
// 错误响应
return errjson('错误信息', 错误码);
// 返回:{"code":0, "errmsg":"错误信息"}
开发规范
文件命名规范
- 控制器文件:
{功能名}.php - 视图文件:
{功能名}.html - 类名与文件名保持一致
- 方法命名:
json_{功能名}(AJAX接口) - API路径:/admin/{功能名}.{方法名}
- 所有代码,均被nginx引导至route.php路由。所有函数均为类静态函数。
代码组织规范
<?php
namespace web\admin;
class {功能名} {
public static function json_init() {
// 1. 验证用户登录
$rsuser = verifyfast();
// 2. 业务逻辑处理
// ...
// 3. 返回数据
return succjson($ret);
}
// 其他功能方法
public static function json_{方法名}() {
// ...
}
}
数据验证规范
// 获取POST参数
$post = new \ciy\post();
// 参数获取和验证
$id = $post->getint('id'); // 获取整数
$name = $post->get('name'); // 获取字符串(自动过滤HTML标签)
$html = $post->get('html', '', 'html'); // 获取字符串(自动过滤,但保留HTML标签)
$content = $post->get('content', '', 'all'); // 获取字符串,不做任何过滤
$price = $post->getfloat('price'); // 获取浮点数
$date = $post->getdate('date'); // 获取日期(转换为时间戳)
$isenable = $post->getbool('enable');// 获取布尔值
错误处理规范
try {
// 业务逻辑
$db->begin();
// ... 数据库操作
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson('操作失败:' . $ex->getMessage());
}
移动端API接口
移动端接口目录定义
- 每个移动端均指定统一目录实现API接口 例如: /web/ambap/ 一般在前端jsnurl中设置。
前端函数调用方法
await this.callfunc({
func: 'main.index_init', // API函数名,格式:模块.方法
data: { // 要发送的数据
id: 123
},
});
后端API接口开发
- 在/web/ambap/建立 main.php文件
- 在main.php文件中建立 json_index_init() 静态函数
- 示例代码:/web/ambap/main.php 文件
<?php
namespace web\ambap;
class main {
public static function json_index_init() {
global $db;
$rsuser = verifyfast(); //如果用户未授权,直接返回JSON,后面代码不执行
$rsuser = verifyuser(); //如果用户未授权,$rsuser = null,不返回
$post = new \ciy\post();
$id = $post->getint('id');
$csql = new \ciy\sql('ap_art_post');
$csql->where('id', $id);
$artrow = $db->getone($csql);
if (!is_array($artrow))
return errjson('文章不存在' . $id);
$ret['data'] = $artrow;
return succjson($ret);
}
}
PC端 增删改查完整示例
本章节通过一个完整的示例演示如何开发增删改查功能。
前端页面参考:/web/admin/demo/normal.html 后端页面参考:/web/admin/demo/normal.php 后端代码如下:
查询列表接口
namespace web\admin\demo;
class normal {
/**
* 查询条件构建(私有方法)
*/
static function setwhere($db, $post, $rsuser) {
$query = $post->get('query', array());
$csql = new \ciy\sql('demo_normal');
// 下拉筛选
$liid = objint($query, 'liid');
if ($liid > 0)
$csql->where('auditstatus', $liid);
// 文本模糊查询(关联字典表)
$val = objstr($query, 'audituser');
if (!empty($val)) {
$csqlt = new \ciy\sql('zc_cata');
$csqlt->where('cbid in (select id from zc_cata where cbid=0 and codeid=\'audituser\')');
$csqlt->where('name like', $val);
$trow = $db->getone($csqlt);
if (is_array($trow)) {
$csql->where('audituser', $trow['codeid']);
$query['audituser'] = $trow['name'];
} else {
$csql->where('audituser=0');
}
}
// 日期范围查询。无需判断,objstr返回空字符串则where自动跳过。
$csql->wheredaterange('audittimes', objstr($query, 'audittimes'));
// 文本模糊查询。无需判断,objstr返回空字符串则where自动跳过。
$csql->where('auditmsg like', objstr($query, 'auditmsg'));
$csql->where('name like', objstr($query, 'name'));
// 关联表查询
$val = objstr($query, 'menuid');
if (!empty($val)) {
$csqlt = new \ciy\sql('zc_menu');
$csqlt->where('name like', $val);
$trow = $db->getone($csqlt);
if (is_array($trow)) {
$csql->where('menuid', $trow['id']);
$query['menuid'] = $trow['name'];
} else {
$csql->where('menuid=0');
}
}
// 单选筛选
$csql->where('isopen', objstr($query, 'isopen'));
// 多选筛选(存储格式:,1,2,)
$csql->where('mauditstatus like', ',' . objstr($query, 'mauditstatus') . ',');
// 数值范围查询(带倍数转换)
$csql->wherenumrange('ton', objstr($query, 'ton_1'), objstr($query, 'ton_2'), 1000000);
// 排序
$order = objstr($query, 'order', 'id desc');
$csql->order($order);
$query['order'] = $order;
return [$query, $csql];
}
/**
* 列表查询接口
*/
public static function json_list() {
global $db;
$rsuser = verifyfast();
$post = new \ciy\post();
// 构建查询条件
list($where, $csql) = self::setwhere($db, $post, $rsuser);
// 排除大字段内容字段
$csql->column('!content,md', $db->getraw('show full fields from demo_normal'));
// 分页
$pageno = $post->getint('pageno', 1);
$pagecount = $post->getint('pagecount', 10);
$csql->limit($pageno, $pagecount);
// 查询数据
$mainrowcount = $post->getint('count');
$mrows = $db->get($csql, $mainrowcount);
if ($mrows === false)
return errjson($db->error);
$ret['searchwhere'] = $where;
$ret['pageno'] = $pageno;
$ret['pagecount'] = $pagecount;
$ret['count'] = $mainrowcount;
$ret['list'] = $mrows;
// 获取字段信息(用于显示配置,字段备注[c]前边有小写逗号,则代表前端不显示该字段)
if ($post->getbool('field')) {
$field = array();
$fshow = $db->getfield($field, 'demo_normal');
foreach ($field as $fr => $v) {
if (get('_' . $fr))
$field[$fr]['c'] = ',' . $field[$fr]['c'];
}
$fshow = fieldadd($fshow, $field, 0, '_btn', '操作');
$ret['field'] = $field;
$ret['fshow'] = $fshow;
}
// 初始化数据(本页面只执行一次)
if ($post->getbool('once')) {
$ret['once'] = true;
$input = array();
$input[] = array('type' => 'input', 'form' => 'audituser', 'name' => '审核人', 'prop' => ' style="width:8em;"');
$input[] = array('type' => 'daterange', 'form' => 'audittimes', 'name' => '审核时间');
$input[] = array('type' => 'input', 'form' => 'auditmsg', 'name' => '审核理由', 'prop' => ' style="width:8em;"');
$input[] = array('type' => 'input', 'form' => 'name', 'name' => '默认标题', 'prop' => ' style="width:8em;"');
$input[] = array('type' => 'input', 'form' => 'menuid', 'name' => '所属菜单', 'prop' => ' style="width:8em;"');
$input[] = array('type' => 'select', 'form' => 'isopen', 'name' => '是否开启', 'all' => '全部', 'select' => '开启.关闭');
$input[] = array('type' => 'select', 'form' => 'mauditstatus', 'name' => '多选状态', 'all' => '全部', 'select' => 'auditstatus');
$input[] = array('type' => 'num', 'form' => 'ton', 'name' => '吨位', 'prop' => ' style="width:4em;"');
$ret['searchinput'] = $input;
// 加载全量关联数据
$csql = (new \ciy\sql('zc_depart'))->column('id,name,upid');
$ret['zc_depart'] = $db->get($csql);
}
// 每页增量关联数据(用于表格显示)
$ret['zc_menu'] = getrelation($db, $mrows, 'zc_menu', 'menuid');
return succjson($ret);
}
}
获取数据接口
/**
* 获取单条数据(用于编辑/查看)
*/
public static function json_getdata() {
global $db;
$rsuser = verifyfast();
$post = new \ciy\post();
$id = $post->getint('id');
$act = $post->get('act');
$mrow = array();
if ($id > 0) {
$csql = new \ciy\sql('demo_normal');
$csql->where('id', $id);
$mrow = $db->getone($csql);
if (!is_array($mrow))
return errjson('数据不存在');
// 根据操作类型加载该条关联数据
if ($act == 'view' || $act == 'review') {
$csql = (new \ciy\sql('zc_menu'))->column('id,name');
$csql->where('id', $mrow['menuid']);
$ret['zc_menu'] = $db->get($csql);
}
}
$ret['data'] = $mrow;
// 编辑时加载所有关联数据
if ($act == 'edit') {
$csql = (new \ciy\sql('zc_menu'))->column('id,name');
$ret['zc_menu'] = $db->get($csql);
}
return succjson($ret);
}
新增/更新接口
/**
* 新增/更新数据
*/
public static function json_update() {
global $db;
$rsuser = verifyfast();
// 权限检查。权限定义在菜单表zc_menu中
// if (nopower($db, $rsuser['id'], 'pxxxu'))
// return errjson('您未被授权操作');
$post = new \ciy\post();
// 获取参数
$id = $post->getint('id');
$name = $post->get('name');
$menuid = $post->getint('menuid');
$filesize = $post->getint('filesize');
$content = $post->get('content');
// ... 其他字段
// 参数验证
if (empty($name))
return errjson('请填写默认标题');
$datarow = null;
if ($id > 0) {
// 更新模式:获取原数据
$csql = new \ciy\sql('demo_normal');
$csql->where('id', $id);
$datarow = $db->getone($csql);
if (!is_array($datarow))
return errjson('数据不存在');
}
try {
$db->begin();
// 构建更新数据
$updata = array();
$updata['name'] = $name;
$updata['menuid'] = $menuid;
$updata['filesize'] = $filesize;
$updata['content'] = $content;
// ... 其他字段
$csql = new \ciy\sql('demo_normal');
if ($id > 0) {
// 更新
$csql->where('id', $id);
if ($db->update($csql, $updata) === false)
throw new \Exception('更新失败:' . $db->error);
} else {
// 新增
$updata['auditstatus'] = 0;
$updata['audituser'] = 0;
$updata['audittimes'] = 0;
$updata['auditmsg'] = '';
$updata['addtimes'] = tostamp();
if ($db->insert($csql, $updata) === false)
throw new \Exception('新增失败:' . $db->error);
$id = $db->insert_id();
}
$updata['id'] = $id;
// 记录日志(可选)
// savelogdb($db, $rsuser['id'], 'demo_normal', $datarow, $updata);
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
savelogfile('err_db', $ex->getMessage());
return errjson($ex->getMessage());
}
// 返回更新后的数据
$ret['data'] = $updata;
$ret['zc_menu'] = getrelation($db, [$updata], 'zc_menu', 'menuid');
return succjson($ret);
}
删除接口
/**
* 删除数据
*/
public static function json_del() {
global $db;
$rsuser = verifyfast();
// 权限检查(可选)
// if (nopower($db, $rsuser['id'], 'pxxxd'))
// return errjson('您未被授权操作');
$post = new \ciy\post();
$ids = $post->get('ids');
if (empty($ids))
return errjson('请选择至少一条');
// 查询要删除的数据
$csql = new \ciy\sql('demo_normal');
$csql->where('id in', $ids);
$mrows = $db->get($csql);
$vids = array();
try {
$db->begin();
foreach ($mrows as $mrow) {
//可针对$mrow做是否可删除判断,不可删除用continue跳过
$delid = $mrow['id'];
// 检查提示有关联数据(可选)
// delcheck($db, $delid, 'tablexx', 'xxid', '关联数据');
// 直接删除关联数据(可选)
// delall($db, $delid, 'tablexx', 'xxid', '关联数据');
// 删除主数据
delme($db, $delid, 'demo_normal');
// 记录日志
savelogdb($db, $rsuser['id'], 'demo_normal', $mrow, null);
$vids[] = $delid;
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
savelogfile('err_db', $ex->getMessage());
return errjson($ex->getMessage());
}
$ret['ids'] = $vids;
return succjson($ret);
}
审核接口(自定义业务)
/**
* 审核接口
*/
public static function json_audit() {
global $db;
$rsuser = verifyfast();
// 权限检查(可选)
// if (nopower($db, $rsuser['id'], 'p a'))
// return errjson('您未被授权操作');
$post = new \ciy\post();
$ids = $post->get('ids');
if (empty($ids))
return errjson('请选择至少一条');
$auditstatus = $post->getint('auditstatus');
$auditmsg = $post->get('auditmsg');
// 驳回时必须填写原因
if ($auditstatus == 90 && empty($auditmsg))
return errjson('请填写驳回原因');
// 查询数据
$csql = new \ciy\sql('demo_normal');
$csql->where('id in', $ids);
$mrows = $db->get($csql);
$ids = array();
try {
$db->begin();
foreach ($mrows as $mrow) {
if ($auditstatus == 100) {
// 审核通过时的额外处理
}
// 更新审核状态
$updata = array();
$updata['auditstatus'] = $auditstatus;
$updata['audituser'] = $rsuser['id'];
$updata['audittimes'] = tostamp();
$updata['auditmsg'] = $auditmsg;
$csql = new \ciy\sql('demo_normal');
$csql->where('id', $mrow['id']);
if ($db->update($csql, $updata) === false)
throw new \Exception('审核失败:' . $db->error);
$ids[] = $mrow['id'];
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
savelogfile('err_db', $ex->getMessage());
return errjson($ex->getMessage());
}
$ret['data'] = $updata;
$ret['ids'] = $ids;
return succjson($ret);
}
核心类库详解
1. 用户认证系统 (common.php)
verifyfast() - 快速用户验证
$rsuser = verifyfast();
- 自动验证用户登录状态
- 未登录时自动返回JSON错误响应,中断后面代码执行
- 超时自动续期
verifyuser() - 用户验证
$rsuser = verifyuser();
- 验证用户登录状态
- 返回用户信息数组或null
- 需要手动处理未登录情况
nopower() - 权限验证
if (nopower($db, $userid, 'x1ssh'))
return errjson('您未被授权操作');
- 检查用户是否有指定权限
- 权限格式:
{模块}{操作},如x1ssh表示SSH模块访问权限 - 超级管理员权限:
.*. - 权限是字符串存储,多个权限两边都有句点。例如 .x1ssh.v4del.
2. 配置管理 (common.php)
getconfig() - 获取配置
$apiid = getconfig($db, 'apiid', '');
- 从
zc_config表读取配置 - 支持默认值
setconfig() - 设置配置
setconfig($db, 'apiid', 'newvalue');
- 更新配置到数据库
- 配置不存在时自动创建
3. 日志系统 (common.php)
savelog() - 保存日志
savelog($db, $userid, 'LOGIN', '用户登录', false);
- 记录操作日志到
zc_log表 - 支持记录完整的请求信息
savelogdb() - 数据库变更日志
savelogdb($db, $userid, 'UPDATE', $oldrow, $newrow);
- 自动记录数据变更
- 对比新旧数据,只记录变更字段
- 添加、修改、删除
数据库操作
1. 数据库连接
global $db;
// $db 自动从配置文件中读取数据库配置并连接
2. SQL构建类 (sql.php)
基本查询
$csql = new \ciy\sql('table_name');
$csql->column('id,name,addtimes');
$csql->where('status', 1);
$csql->order('addtimes desc');
$rows = $db->get($csql);
分页查询
$csql = new \ciy\sql('table_name');
$rowcount = 0;
$rows = $db->get($csql, $rowcount); // 返回总数据条数
单条查询
$csql = new \ciy\sql('table_name');
$csql->where('id', $id);
$mrow = $db->getone($csql); //可能返回Array有数据、Null无数据、false数据库错误
if(!is_array($mrow))
return errjson('没找到数据');
查询单个字段
$csql = new \ciy\sql('table_name');
$csql->column('name');
$csql->where('id', $id);
$name = $db->get1($csql);
统计数据
$csql = new \ciy\sql('table_name');
$csql->column('count(*)');
$csql->where('status', 1);
$count = $db->get1($csql);
条件查询
// LIKE查询
$csql->where('name like', 'keyword');
// IN查询
$csql->where('id in', '1,2,3');
// 范围查询
$csql->where('price>=', 100);
$csql->where('price<=', 500);
// 日期范围
$csql->wheredaterange('addtimes', '2024-01-01~2024-12-31', 'date');
$csql->wheredaterange('addtimes', '-7~7', 'day'); //查询上周至下周的近两周数据
$csql->wheredaterange('addtimes', '2024-01', 'month'); //查询2024年1月,整月数据
// 数值范围
$csql->wherenumrange('price', '', '20', 100); //查询小于20元的数据,数据表实际按分存储,20元=2000,100为倍数,默认1。
$csql->wherenumber('tempc', '38~', 1000); //查询大于38度的数据,原始数据,例如38.23°,数据库实际存储整数:38230
$csql->wherenumber('status', '>=10'); //查询大于等于10的数据。
// 多参数符合查询
$csql->where('(name like ? or prodname like ?) and status=?', ['名字', '产品', 10]);
// 自由查询(非必要不使用)
$csql->where('id=3');
// 添加sql末尾数据
$csql->selecttail('group by xxx');
// 选项卡筛选(状态类型筛选)
$liid = objint($query, 'liid');
if ($liid === 1)
$csql->where('xxx>', 0); // liid=1选项卡
else if ($liid === 2)
$csql->where('xxstatus', 10);// liid=2选项卡
else
$csql->where('exptimes>', tostamp());// 默认选项卡
if ($liid > 0)
$csql->where('xxxtype', $liid); // 字典项选项卡
// 时间范围筛选(未到期/已到期)
if ($liid === 1)
$csql->where('expiretimes>', tostamp()); // 未到期
else if ($liid === 2)
$csql->where('expiretimes<=', tostamp()); // 已到期
// 按关联表名称查询
$topicid = objstr($query, 'topicid');
if (!empty($topicid)) {
$csqlt = new \ciy\sql('am_topicbase');
$csqlt->where('name like', $topicid);
$trow = $db->getone($csqlt);
if (is_array($trow)) {
$csql->where('topicid', $trow['id']);
$query['topicid'] = $trow['name'];
} else {
$csql->where('topicid=0');
}
}
3. 数据操作
插入数据
$updata = array(
'name' => '测试',
'status' => 1,
'addtimes' => tostamp()
);
$csql = new \ciy\sql('table_name');
$db->insert($csql, $updata);
$newid = $db->insert_id();
更新数据
$updata = array(
'name' => '更新后的名称',
'status' => 2
);
$csql = new \ciy\sql('table_name');
$csql->where('id', $id);
$db->update($csql, $updata);
删除数据
$csql = new \ciy\sql('table_name');
$csql->where('id', $id);
$db->delete($csql); // 直接删除
$db->delete($csql, true); // 删除前备份到table_name_bak表
删除数据(批量)
public static function json_del() {
global $db;
$rsuser = verifyfast();
$post = new \ciy\post();
$ids = $post->get('ids');
if (empty($ids))
return errjson('请选择至少一条');
$csql = new \ciy\sql('table_name');
$csql->where('id in', $ids);
$mrows = $db->get($csql);
$vids = array();
try {
$db->begin();
foreach ($mrows as $mrow) {
$delid = $mrow['id'];
//delcheck($db, $delid, 'tablexx', 'xxid', '管理员'); //发现该表有相关数据不允许删除
//delall($db, $delid, 'tablexx', 'xxid', '运动员'); //该表有相关数据一起直接删除
delme($db, $delid, 'table_name');
savelogdb($db, $rsuser['id'], 'table_name', $mrow, null);
$vids[] = $delid;
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
savelogfile('err_db', $ex->getMessage());
return errjson($ex->getMessage());
}
$ret['ids'] = $vids;
return succjson($ret);
}
前端调用:
<a class="btn dag" onclick="ciyfn.select_callfunc(table, this, 'del','已选{n}条,是否批量删除?', {},function(json){table.delline(json)})">批量删除</a>
要点:
- 使用
ciyfn.select_callfunc()框架方法处理批量选择和确认 - 后端返回删除的ID数组
$ret['ids'] - 前端通过
table.delline(json)自动删除表格中的对应行 - 批量删除按钮使用
dagclass(红色警告样式)
统一的事务处理
try {
$db->begin();
// 多个数据库操作
$db->insert($csql1, $data1);
$db->update($csql2, $data2);
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson('操作失败:' . $ex->getMessage());
}
return succjson();
用户认证与权限
登录流程 (login.php)
框架已实现,不定制新需求,无需生成新代码。
数据字典缓存机制 登录时会一次性返回所有字典数据到前端
前端使用字典数据
// 使用字典数据
var statusList = ciyfn.getstorage('cata_auditstatus');
Token机制
Token配置 (common.php)
$_token['type'] = 'cookie'; // 存储方式:cookie/localstorage。移动端为兼容微信小程序,需使用localstorage
$_token['swapsec'] = 3600; // 更换Token时间(秒)
$_token['expsec'] = 86400 * 30; // 过期退出时间(秒)
$_token['field'] = 'ciyadm'; // Cookie字段名
$_token['salt'] = 'bka02$59gG'; // 加密盐值
Token验证流程
- 客户端发送请求时携带Token
- 服务端解密Token获取用户ID
- 从
zc_online表验证会话有效性 - 超时自动续期,返回新Token
权限控制
权限格式
- 格式:
{模块}{操作} - 示例:
x1ssh表示SSH模块访问权限 - 超级管理员:
.*.
权限验证
// 检查用户是否有权限
if (nopower($db, $rsuser['id'], 'x1ssh'))
return errjson('您未被授权操作');
// 前端权限检查
if (ciyfn.nopower(me.power, 'x1ssh'))
continue; // 无权限
API开发规范
PC端接口命名规范
- 所有AJAX接口方法名以
json_开头 - 方法名采用小写+下划线格式
- 示例:
json_init,json_update,json_del
移动端接口命名规范
-
API方法名应与前端页面文件名对应,便于识别和维护。格式建议:
json_{vue页面名}_{操作}- 示例:
aimap.php文件中的函数json_index_init对应/pages/aimap/index.vue,前端调用func: 'aimap.index_init' - 示例:
json_aimap_msglst_init对应/pages/aimap/msglst.vue,前端调用func: 'aimap.msglst_init'
- 示例:
-
用户认证接口复用:注册、登录、找回密码等通用认证接口已内置,无需重复开发,直接复用框架提供的
ciy-auth组件接口 -
首次请求优化:页面首次加载使用
init接口,通过once标志位一次性返回所有初始化数据,减少请求次数
接口参数处理
public static function json_userlist() {
// 1. 验证用户登录
$rsuser = verifyfast();
// 2. 获取参数
$post = new \ciy\post();
$pageno = $post->getint('pageno', 1);
$pagecount = $post->getint('pagecount', 20);
$keyword = $post->get('keyword');
// 3. 业务逻辑处理
$csql = new \ciy\sql('zc_admin');
if (!empty($keyword)) //通常无需判断,where子句会判断$keyword是否为空
$csql->where('name like', $keyword);
$csql->order('addtimes desc');
$rowcount = -1; //rowcount=-1 则需要count(*)获得数据统计,>-1则跳过统计查询,避免翻页时频繁查询统计数据。
$rows = $db->get($csql, $rowcount);
// 4. 返回数据
$ret['rows'] = $rows;
$ret['rowcount'] = $rowcount;
return succjson($ret);
}
首次请求优化
/**
* 页面初始化接口
* 通过once标志位一次性返回所有初始化数据
*/
public static function json_aimap_index_init() {
global $db;
$rsuser = verifyfast();
$post = new \ciy\post();
if ($post->getbool('once')) { //仅页面首次加载,翻页请求不再执行
$ret['once'] = true;
$csql = new \ciy\sql('table_name');
$ret['tabxxx'] = $db->get($csql);
}
$csql = new \ciy\sql('table_xxx');
$csql->order('addtimes desc');
$csql->limit($post->getint('pageno', 1), 20);
$ret['list'] = $db->get($csql);
return succjson($ret);
}
错误处理
// 参数验证
if (empty($id))
return errjson('ID不能为空');
// 数据验证
if ($rsuser === null)
return errjson($db->error);
// 数据是否存在
$mrow = $db->getone(xxx);
if (!is_array($mrow)) //mrow有可能返回null或false,因此需使用is_array()
return errjson($db->error);
// 权限验证
if (nopower($db, $rsuser['id'], 'x1user'))
return errjson('您未被授权操作');
最佳实践
数据库操作优化
批量操作
// 使用事务进行批量操作
$db->begin();
try {
foreach ($datalist as $data) {
$db->insert($csql, $data);
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
}
避免N+1查询
// 使用关联查询,按需查询可视化需要的数据
// 将$rows中userid数据去重后,查询 select id,name from table_name where id in (userid1,userid2,...),返回{id,name}数组
$ret['xxtable'] = getrelation($db, $rows, 'table_name', 'userid', 'id,name');
安全最佳实践
SQL注入防护
// ✅ 使用参数化查询
$csql->where('id', $id);
$db->get($csql);
// ❌ 避免字符串拼接
$sql = "select * from table where id = " . $id;
XSS防护
// 使用post类自动过滤
$post = new \ciy\post();
$name = $post->get('name'); // 自动strip_tags
// 需要HTML内容时
$html = $post->get('content', '', 'html'); // 安全过滤HTML
CSRF防护
// 验证请求来源
if (!isset($_SERVER['HTTP_REFERER']))
return errjson('非法请求');
性能优化
分页优化
// 大数据量分页
$rowcount = -1; // 只有赋值-1时,才查询总数,其他数值则不查询总数
$csql->column('*');
$csql->limit($pageno, $pagecount);
$rows = $db->get($csql, $rowcount);
代码复用
公共函数封装
// 在cwebcomon.php中定义公共函数,只有1处调用时,不要定义到公共函数。
namespace web;
class cwebcomon {
function getuserlist($db, $keyword = '') {
$csql = new \ciy\sql('zc_admin');
if (!empty($keyword))
$csql->where('name like', $keyword);
$csql->order('addtimes desc');
return $db->get($csql);
}
}
公共类库使用
$rows = \web\cwebcomon::getuserlist($db, $keyword);
日期工具
\ciy\sql::daterange('2024-01-01~2024-12-31', 'date'); //返回时间戳,与sql的wheredaterange函数机制一致
数据字典设计规范
参考 ciyon-数据字典设计.md
附录
数据库表结构
已存在的核心表
zc_admin- 管理员表zc_role- 角色表zc_depart- 组织机构表zc_online- 在线会话表zc_cata- 数据字典表zc_menu- 菜单表zc_mnufav- 收藏菜单表zc_log- 操作日志表zc_lug- 登录日志表zc_config- 配置表zc_autotask- 自动任务表zc_mq- 消息队列表 需DB事务级一致,可用此队列过渡
comm.php 公共函数库详解
comm.php是Ciyon框架的核心公共函数库,提供了丰富的工具函数,涵盖日期处理、加密解密、字符串处理、数据库操作、文件操作等多个方面。
日期时间函数
tostamp() - 转换为时间戳
// 字符串转时间戳
$timestamp = tostamp('2024-03-14 10:30:00');
// 数字转时间戳
$timestamp = tostamp(1710385800);
// 当前时间
$timestamp = tostamp('now');
todate() - 时间戳转字符串
// 默认格式:Y-m-d H:i
$date = todate(1710385800); // '2024-03-14 10:30'
// 指定格式
$date = todate(1710385800, 'd'); // '2024-03-14'
$date = todate(1710385800, 's'); // '2024-03-14 10:30:00'
$date = todate(1710385800, 'm'); // '2024-03'
$date = todate(1710385800, 'H'); // '2024-03-14 10'
$date = todate(1710385800, 'hi'); // '10:30'
fixmonth() - 月份加减修正
// 修正PHP月份加减的错误(如1月31日加1个月应为2月28/29日)
$newdate = fixmonth(1, strtotime('2024-01-31')); // 返回2月最后一天
totimepoint() - 数字时钟转字符串
// 将秒数转换为 HH:MM:SS 格式 范围为1-3600,0代表未设置
$time = totimepoint(1801); // '12:00'
$time = totimepoint(1, true); // '00:00:00'
tostamp() - 字符串转时间戳
// 多种格式支持
$timestamp = tostamp('2024-03-14');
$timestamp = tostamp('2024-03-14 10:30:00');
$timestamp = tostamp('1710385800');
$timestamp = tostamp('now');
数据类型转换
tostr() - 转字符串
$str = tostr($value, ''); // 默认返回空字符串
toint() - 转整数
$int = toint($value); // 默认返回0
$int = toint($value, -1); // 默认返回-1
tofloat() - 转浮点数
$float = tofloat($value); // 默认返回0.0
$float = tofloat($value, 0.0); // 默认返回0.0
加密解密函数
encrypt() - 字符串加解密
// 加密
$encrypted = encrypt('原始数据', 'E', '密钥');
// 解密
$decrypted = encrypt($encrypted, 'D', '密钥');
// 示例
$key = 'bka02$59gG'; // 每个项目应该使用不同的密钥
$encrypted = encrypt('Hello World', 'E', $key);
$decrypted = encrypt($encrypted, 'D', $key); // 返回 'Hello World'
enid()/deid() - ID数字加解密
// 加密ID(防爬虫)
$encrypted_id = enid(12345); // 返回如 '872435'
// 解密ID
$decrypted_id = deid(872435); // 返回 12345
// 注意:加密后的ID看起来像普通数字,但包含校验位
sha256()/sha512() - 哈希函数
// SHA256哈希
$hash = sha256('password'); // 返回64位十六进制字符串
// SHA512哈希
$hash = sha512('password'); // 返回128位十六进制字符串
conv33_10()/conv10_33() - 33进制转换
// 33进制转10进制
$decimal = conv33_10('abc123');
// 10进制转33进制
$base33 = conv10_33(123456);
字符串处理
gb_substr() - 中文字符串截取
// 截取中文字符串(中文字符算2个位置)
$text = gb_substr('中华人民共和国', 10); // '中华...'
$text = gb_substr('中华人民共和国', 9); // '中华...'
gb_strlen() - 中文字符串长度
// 计算中文字符串长度(中文字符算2个)
$len = gb_strlen('中华人民共和国'); // 返回 28
gb_haschinese() - 检测中文字符
// 检测字符串是否包含中文
$has = gb_haschinese('Hello世界'); // 返回 true
getstrparam()/setstrparam() - 简化数据保存
// 解析简化格式数据
$data = getstrparam('name=张三|age=18|city=北京');
$data = getstrparam('name=张三&age=18&city=北京', '&');
// 返回:['name' => '张三', 'age' => '18', 'city' => '北京']
// 生成简化格式数据
$str = setstrparam(['name' => '李四', 'age' => 20, 'city' => '上海']);
// 返回:'name=李四|age=20|city=上海'
$str = setstrparam(['name' => '李四', 'age' => 20, 'city' => '上海'], '&');
// 返回:'name=李四&age=20&city=上海'
startwith()/endwith() - 首尾匹配
// 检查字符串开头
$starts = startwith('Hello World', 'Hello'); // 返回 true
// 检查字符串结尾
$ends = endwith('Hello World', 'World'); // 返回 true
数据验证
ismobile() - 手机号验证
// 验证中国大陆手机号
$valid = ismobile('13800138000'); // 返回 true
$valid = ismobile('12345'); // 返回 false
idcard() - 身份证验证
// 验证身份证号并提取信息
$info = idcard('330106199001011234');
// 返回:[
// 'code' => '330106199001011234',
// 'birth' => '1990-01-01',
// 'sex' => 1, // 1=男, 2=女
// 'area' => '330106'
// ]
// 验证失败返回错误信息
$info = idcard('123456'); // 返回 '身份证长度错误'
iduscc() - 统一社会信用代码验证
// 验证统一社会信用代码
$info = iduscc('91330100MA2XXX001X');
// 返回:['code' => '91330100MA2XXX001X', 'area' => '33', 'pn' => '91']
locinzone() - 经纬度围栏检测
// 检测经纬度是否在围栏内
$fence = '30.434272 120.274938,30.433977 120.277270,30.432403 120.277957';
$inside = locinzone($fence, 30.433, 120.275); // 返回 true
locdistance() - 计算距离
// 计算两个经纬度之间的距离(毫米)
$distance = locdistance(30.25, 120.15, 30.26, 120.16);
// 返回:距离(毫米)
字典数据函数
ccode() - 代码转名称
// 单个代码转名称
$arr = [
['id' => 1, 'name' => '启用'],
['id' => 2, 'name' => '禁用']
];
$name = ccode($arr, 1, 'name', '--'); // 返回 '启用'
// 返回对象
$obj = ccode($arr, 1, '_obj'); // 返回 ['id' => 1, 'name' => '启用']
mcode() - 多级代码转名称
// 多级代码转名称数组(如地区、部门)
$arr = [
['id' => 1, 'name' => '浙江', 'upid' => 0],
['id' => 2, 'name' => '杭州', 'upid' => 1],
['id' => 3, 'name' => '西湖', 'upid' => 2]
];
$names = mcode($arr, 3, 'name'); // 返回 ['浙江', '杭州', '西湖']
scode() - 多个代码转名称数组
// 多个代码转名称数组
$arr = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B']];
$names = scode($arr, '1,2', 'name'); // 返回 ['A', 'B']
dcode() - 名称转代码
// 名称转代码
$arr = [['id' => 1, 'name' => '启用'], ['id' => 2, 'name' => '禁用']];
$id = dcode($arr, '启用', 'id'); // 返回 1
bcode() - 位标志转名称
// 位标志转名称数组(用于多选)
$arr = [
['id' => 1, 'name' => 'A'],
['id' => 2, 'name' => 'B'],
['id' => 4, 'name' => 'C']
];
$names = bcode($arr, 5); // 5 = 1+4,返回 ['A', 'C']
文件操作
dirmake() - 创建目录
// 创建多层目录
$result = dirmake('/path/to/dir', 0777);
filedel() - 删除文件
// 静默删除文件(不报错)
filedel('/path/to/file.txt');
filecopy() - 复制文件
// 静默复制文件
filecopy('/source/file.txt', '/dest/file.txt');
filesave() - 保存文件
// 保存文本到文件
$result = filesave('/path/to/file.txt', '文件内容');
fileload() - 读取文件
// 读取文本文件
$content = fileload('/path/to/file.txt');
storsave() - 保存到存储目录
// 保存到存储目录(支持日期变量)
storsave('{Y}/{m}{d}/data.txt', '内容');
// 保存到:/web/ud/2024/0314/data.txt
file_down() - 下载文件
// 下载远程文件到本地
$path = file_down(
'https://example.com/image.jpg', // 远程URL
'/ud/images/', // 保存路径
'', // 文件名(空则自动生成)
60, // 超时时间(秒)
'.jpg' // 默认扩展名
);
file_ext() - 获取文件扩展名
// 获取文件扩展名
$ext = file_ext('/path/to/file.txt'); // 返回 'txt'
file_stor() - 获取文件的云存储URL
// 获取文件访问URL(支持云存储)
$url = file_stor('/2024/03/14/image.jpg');
// 返回完整的访问URL
注意:前端显示附件时,必须使用 ciyfn.file_stor() 将数据库存储的相对路径转换为云存储绝对URL。前端调用时:
var imgurl = ciyfn.file_stor('/2024/03/14/image.jpg');
// 返回完整的云存储URL(原始文件)
var thumburl = ciyfn.file_stor('/2024/03/14/image.jpg', 'thumb');
// 返回缩略图云存储URL(图片资源用)
配置和输入
webini() - 读取配置文件
// 读取web.ini配置
$config = webini('db');
// 返回:['host' => 'localhost', 'port' => '3306', ...]
$config = webini('s3');
// 返回:['url' => 'https://...', ...]
getstr()/getint()/post()/request()/cookie() - 获取输入
// 获取GET参数
$name = getstr('name', '', 'text'); // 第三个参数:text(过滤HTML)、all(不过滤)
$age = getint('age', 0);
// 获取POST参数
$name = post('name', '', 'text');
// 获取REQUEST参数
$name = request('name', '', 'text');
// 获取Cookie
$name = cookie('name', '', 'text');
getip() - 获取真实IP
// 获取客户端真实IP(支持CDN、反向代理)
$ip = getip(); // 返回'80.15.128.210'
日志和调试
savelogfile() - 保存日志到文件
// 保存日志到文件
savelogfile('error', '错误信息');
// 保存到:/web/log/error.log
// 包含请求信息
savelogfile('api', 'API调用', true);
logdbstr() - 格式化数据变更
// 格式化数据变更日志
$oldrow = ['id' => 1, 'name' => '旧名称', 'status' => 1];
$newrow = ['id' => 1, 'name' => '新名称', 'status' => 2];
$msg = logdbstr($oldrow, $newrow);
// 返回:'Upd=1_|@|_name=旧名称→新名称_|@|_status=1→2'
clog() - 调试输出
// 调试输出变量
clog($variable);
clog('Title', $variable);
clog($var1, $var2, $var3);
杂项工具
randstr() - 生成随机字符串
// 生成随机字符串
$str = randstr(10); // 生成10位随机字符串
$str = randstr(10, 'abcdef123456'); // 使用指定字符集
arrayrand() - 随机抽取数组元素
// 随机抽取数组元素并删除
$arr = ['A', 'B', 'C', 'D', 'E'];
$selected = arrayrand($arr, 2); // 返回 ['A', 'C'],原数组变为 ['B', 'D', 'E']
timems() - 获取微秒时间
// 获取当前微秒数
$ms = timems(); // 返回如 1710385800123
常用函数索引
用户相关
verifyfast()- 快速用户验证verifyuser()- 用户验证nopower()- 权限验证
配置相关
getconfig()- 获取配置setconfig()- 设置配置
日志相关
savelog()- 保存日志savelogdb()- 数据库变更日志
工具函数
tostamp()- 转换为时间戳todate()- 时间戳转字符串randstr()- 生成随机字符串getip()- 获取IP地址
api json错误代码
| 代码 | 说明 |
|---|---|
| 1 | 成功 |
| 0 | 失败 |
| 2 | 未登录 |
| 3 | 未授权 |
| 9 | 函数未加载 |
开发环境配置
数据库配置 (web.ini)
[db]
host = localhost
port = 3306
database = c5_ciyon
username = root
password = wkxroot
charset = utf8mb4
总结
Ciyon后台管理系统是一个功能完善、架构清晰的PHP开发框架。通过本文档,开发者可以快速理解系统的核心架构和开发规范,高效地进行二次开发和功能扩展。
核心优势:
- 完整的用户认证和权限控制系统
- 安全的数据库操作层
- 灵活的模块化设计
- 完善的错误处理机制
- 丰富的工具类库
文档版本:1.0
技术支持:众产® https://ciy.cn/code