# 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` - 核心工具类命名空间 ### 控制器结构 控制器类采用静态方法模式,每个功能对应一个静态方法: ```php 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格式响应: ```php // 成功响应 $ret['data'] = array(); return succjson($ret); // 返回:{"code":1, "data":{...}} // 错误响应 return errjson('错误信息', 错误码); // 返回:{"code":0, "errmsg":"错误信息"} ``` --- ## 开发规范 ### 文件命名规范 - 控制器文件:`{功能名}.php` - 视图文件:`{功能名}.html` - 类名与文件名保持一致 - 方法命名:`json_{功能名}`(AJAX接口) - API路径:/admin/{功能名}.{方法名} - 所有代码,均被nginx引导至route.php路由。所有函数均为类静态函数。 ### 代码组织规范 ```php 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');// 获取布尔值 ``` ### 错误处理规范 ```php 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 文件 ``` 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 后端代码如下: #### 查询列表接口 ```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); } } ``` #### 获取数据接口 ```php /** * 获取单条数据(用于编辑/查看) */ 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); } ``` #### 新增/更新接口 ```php /** * 新增/更新数据 */ 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); } ``` #### 删除接口 ```php /** * 删除数据 */ 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); } ``` #### 审核接口(自定义业务) ```php /** * 审核接口 */ 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() - 快速用户验证 ```php $rsuser = verifyfast(); ``` - 自动验证用户登录状态 - 未登录时自动返回JSON错误响应,中断后面代码执行 - 超时自动续期 #### verifyuser() - 用户验证 ```php $rsuser = verifyuser(); ``` - 验证用户登录状态 - 返回用户信息数组或null - 需要手动处理未登录情况 #### nopower() - 权限验证 ```php if (nopower($db, $userid, 'x1ssh')) return errjson('您未被授权操作'); ``` - 检查用户是否有指定权限 - 权限格式:`{模块}{操作}`,如 `x1ssh` 表示SSH模块访问权限 - 超级管理员权限:`.*.` - 权限是字符串存储,多个权限两边都有句点。例如 .x1ssh.v4del. ### 2. 配置管理 (common.php) #### getconfig() - 获取配置 ```php $apiid = getconfig($db, 'apiid', ''); ``` - 从 `zc_config` 表读取配置 - 支持默认值 #### setconfig() - 设置配置 ```php setconfig($db, 'apiid', 'newvalue'); ``` - 更新配置到数据库 - 配置不存在时自动创建 ### 3. 日志系统 (common.php) #### savelog() - 保存日志 ```php savelog($db, $userid, 'LOGIN', '用户登录', false); ``` - 记录操作日志到 `zc_log` 表 - 支持记录完整的请求信息 #### savelogdb() - 数据库变更日志 ```php savelogdb($db, $userid, 'UPDATE', $oldrow, $newrow); ``` - 自动记录数据变更 - 对比新旧数据,只记录变更字段 - 添加、修改、删除 --- ## 数据库操作 ### 1. 数据库连接 ```php global $db; // $db 自动从配置文件中读取数据库配置并连接 ``` ### 2. SQL构建类 (sql.php) #### 基本查询 ```php $csql = new \ciy\sql('table_name'); $csql->column('id,name,addtimes'); $csql->where('status', 1); $csql->order('addtimes desc'); $rows = $db->get($csql); ``` #### 分页查询 ```php $csql = new \ciy\sql('table_name'); $rowcount = 0; $rows = $db->get($csql, $rowcount); // 返回总数据条数 ``` #### 单条查询 ```php $csql = new \ciy\sql('table_name'); $csql->where('id', $id); $mrow = $db->getone($csql); //可能返回Array有数据、Null无数据、false数据库错误 if(!is_array($mrow)) return errjson('没找到数据'); ``` #### 查询单个字段 ```php $csql = new \ciy\sql('table_name'); $csql->column('name'); $csql->where('id', $id); $name = $db->get1($csql); ``` #### 统计数据 ```php $csql = new \ciy\sql('table_name'); $csql->column('count(*)'); $csql->where('status', 1); $count = $db->get1($csql); ``` #### 条件查询 ```php // 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. 数据操作 #### 插入数据 ```php $updata = array( 'name' => '测试', 'status' => 1, 'addtimes' => tostamp() ); $csql = new \ciy\sql('table_name'); $db->insert($csql, $updata); $newid = $db->insert_id(); ``` #### 更新数据 ```php $updata = array( 'name' => '更新后的名称', 'status' => 2 ); $csql = new \ciy\sql('table_name'); $csql->where('id', $id); $db->update($csql, $updata); ``` #### 删除数据 ```php $csql = new \ciy\sql('table_name'); $csql->where('id', $id); $db->delete($csql); // 直接删除 $db->delete($csql, true); // 删除前备份到table_name_bak表 ``` #### 删除数据(批量) ```php 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); } ``` **前端调用**: ```javascript 批量删除 ``` **要点**: - 使用 `ciyfn.select_callfunc()` 框架方法处理批量选择和确认 - 后端返回删除的ID数组 `$ret['ids']` - 前端通过 `table.delline(json)` 自动删除表格中的对应行 - 批量删除按钮使用 `dag` class(红色警告样式) #### 统一的事务处理 ```php 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) 框架已实现,不定制新需求,无需生成新代码。 **数据字典缓存机制** 登录时会一次性返回所有字典数据到前端 **前端使用字典数据** ```javascript // 使用字典数据 var statusList = ciyfn.getstorage('cata_auditstatus'); ``` ### Token机制 #### Token配置 (common.php) ```php $_token['type'] = 'cookie'; // 存储方式:cookie/localstorage。移动端为兼容微信小程序,需使用localstorage $_token['swapsec'] = 3600; // 更换Token时间(秒) $_token['expsec'] = 86400 * 30; // 过期退出时间(秒) $_token['field'] = 'ciyadm'; // Cookie字段名 $_token['salt'] = 'bka02$59gG'; // 加密盐值 ``` #### Token验证流程 1. 客户端发送请求时携带Token 2. 服务端解密Token获取用户ID 3. 从 `zc_online` 表验证会话有效性 4. 超时自动续期,返回新Token ### 权限控制 #### 权限格式 - 格式:`{模块}{操作}` - 示例:`x1ssh` 表示SSH模块访问权限 - 超级管理员:`.*.` #### 权限验证 ```php // 检查用户是否有权限 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` 标志位一次性返回所有初始化数据,减少请求次数 ### 接口参数处理 ```php 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); } ``` ### 首次请求优化 ```php /** * 页面初始化接口 * 通过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); } ``` ### 错误处理 ```php // 参数验证 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('您未被授权操作'); ``` ## 最佳实践 ### 数据库操作优化 #### 批量操作 ```php // 使用事务进行批量操作 $db->begin(); try { foreach ($datalist as $data) { $db->insert($csql, $data); } $db->commit(); } catch (\Exception $ex) { $db->rollback(); } ``` #### 避免N+1查询 ```php // 使用关联查询,按需查询可视化需要的数据 // 将$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注入防护 ```php // ✅ 使用参数化查询 $csql->where('id', $id); $db->get($csql); // ❌ 避免字符串拼接 $sql = "select * from table where id = " . $id; ``` #### XSS防护 ```php // 使用post类自动过滤 $post = new \ciy\post(); $name = $post->get('name'); // 自动strip_tags // 需要HTML内容时 $html = $post->get('content', '', 'html'); // 安全过滤HTML ``` #### CSRF防护 ```php // 验证请求来源 if (!isset($_SERVER['HTTP_REFERER'])) return errjson('非法请求'); ``` ### 性能优化 #### 分页优化 ```php // 大数据量分页 $rowcount = -1; // 只有赋值-1时,才查询总数,其他数值则不查询总数 $csql->column('*'); $csql->limit($pageno, $pagecount); $rows = $db->get($csql, $rowcount); ``` ### 代码复用 #### 公共函数封装 ```php // 在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); } } ``` #### 公共类库使用 ```php $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() - 转换为时间戳** ```php // 字符串转时间戳 $timestamp = tostamp('2024-03-14 10:30:00'); // 数字转时间戳 $timestamp = tostamp(1710385800); // 当前时间 $timestamp = tostamp('now'); ``` **todate() - 时间戳转字符串** ```php // 默认格式: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 // 修正PHP月份加减的错误(如1月31日加1个月应为2月28/29日) $newdate = fixmonth(1, strtotime('2024-01-31')); // 返回2月最后一天 ``` **totimepoint() - 数字时钟转字符串** ```php // 将秒数转换为 HH:MM:SS 格式 范围为1-3600,0代表未设置 $time = totimepoint(1801); // '12:00' $time = totimepoint(1, true); // '00:00:00' ``` **tostamp() - 字符串转时间戳** ```php // 多种格式支持 $timestamp = tostamp('2024-03-14'); $timestamp = tostamp('2024-03-14 10:30:00'); $timestamp = tostamp('1710385800'); $timestamp = tostamp('now'); ``` #### 数据类型转换 **tostr() - 转字符串** ```php $str = tostr($value, ''); // 默认返回空字符串 ``` **toint() - 转整数** ```php $int = toint($value); // 默认返回0 $int = toint($value, -1); // 默认返回-1 ``` **tofloat() - 转浮点数** ```php $float = tofloat($value); // 默认返回0.0 $float = tofloat($value, 0.0); // 默认返回0.0 ``` #### 加密解密函数 **encrypt() - 字符串加解密** ```php // 加密 $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数字加解密** ```php // 加密ID(防爬虫) $encrypted_id = enid(12345); // 返回如 '872435' // 解密ID $decrypted_id = deid(872435); // 返回 12345 // 注意:加密后的ID看起来像普通数字,但包含校验位 ``` **sha256()/sha512() - 哈希函数** ```php // SHA256哈希 $hash = sha256('password'); // 返回64位十六进制字符串 // SHA512哈希 $hash = sha512('password'); // 返回128位十六进制字符串 ``` **conv33_10()/conv10_33() - 33进制转换** ```php // 33进制转10进制 $decimal = conv33_10('abc123'); // 10进制转33进制 $base33 = conv10_33(123456); ``` #### 字符串处理 **gb_substr() - 中文字符串截取** ```php // 截取中文字符串(中文字符算2个位置) $text = gb_substr('中华人民共和国', 10); // '中华...' $text = gb_substr('中华人民共和国', 9); // '中华...' ``` **gb_strlen() - 中文字符串长度** ```php // 计算中文字符串长度(中文字符算2个) $len = gb_strlen('中华人民共和国'); // 返回 28 ``` **gb_haschinese() - 检测中文字符** ```php // 检测字符串是否包含中文 $has = gb_haschinese('Hello世界'); // 返回 true ``` **getstrparam()/setstrparam() - 简化数据保存** ```php // 解析简化格式数据 $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() - 首尾匹配** ```php // 检查字符串开头 $starts = startwith('Hello World', 'Hello'); // 返回 true // 检查字符串结尾 $ends = endwith('Hello World', 'World'); // 返回 true ``` #### 数据验证 **ismobile() - 手机号验证** ```php // 验证中国大陆手机号 $valid = ismobile('13800138000'); // 返回 true $valid = ismobile('12345'); // 返回 false ``` **idcard() - 身份证验证** ```php // 验证身份证号并提取信息 $info = idcard('330106199001011234'); // 返回:[ // 'code' => '330106199001011234', // 'birth' => '1990-01-01', // 'sex' => 1, // 1=男, 2=女 // 'area' => '330106' // ] // 验证失败返回错误信息 $info = idcard('123456'); // 返回 '身份证长度错误' ``` **iduscc() - 统一社会信用代码验证** ```php // 验证统一社会信用代码 $info = iduscc('91330100MA2XXX001X'); // 返回:['code' => '91330100MA2XXX001X', 'area' => '33', 'pn' => '91'] ``` **locinzone() - 经纬度围栏检测** ```php // 检测经纬度是否在围栏内 $fence = '30.434272 120.274938,30.433977 120.277270,30.432403 120.277957'; $inside = locinzone($fence, 30.433, 120.275); // 返回 true ``` **locdistance() - 计算距离** ```php // 计算两个经纬度之间的距离(毫米) $distance = locdistance(30.25, 120.15, 30.26, 120.16); // 返回:距离(毫米) ``` #### 字典数据函数 **ccode() - 代码转名称** ```php // 单个代码转名称 $arr = [ ['id' => 1, 'name' => '启用'], ['id' => 2, 'name' => '禁用'] ]; $name = ccode($arr, 1, 'name', '--'); // 返回 '启用' // 返回对象 $obj = ccode($arr, 1, '_obj'); // 返回 ['id' => 1, 'name' => '启用'] ``` **mcode() - 多级代码转名称** ```php // 多级代码转名称数组(如地区、部门) $arr = [ ['id' => 1, 'name' => '浙江', 'upid' => 0], ['id' => 2, 'name' => '杭州', 'upid' => 1], ['id' => 3, 'name' => '西湖', 'upid' => 2] ]; $names = mcode($arr, 3, 'name'); // 返回 ['浙江', '杭州', '西湖'] ``` **scode() - 多个代码转名称数组** ```php // 多个代码转名称数组 $arr = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B']]; $names = scode($arr, '1,2', 'name'); // 返回 ['A', 'B'] ``` **dcode() - 名称转代码** ```php // 名称转代码 $arr = [['id' => 1, 'name' => '启用'], ['id' => 2, 'name' => '禁用']]; $id = dcode($arr, '启用', 'id'); // 返回 1 ``` **bcode() - 位标志转名称** ```php // 位标志转名称数组(用于多选) $arr = [ ['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B'], ['id' => 4, 'name' => 'C'] ]; $names = bcode($arr, 5); // 5 = 1+4,返回 ['A', 'C'] ``` #### 文件操作 **dirmake() - 创建目录** ```php // 创建多层目录 $result = dirmake('/path/to/dir', 0777); ``` **filedel() - 删除文件** ```php // 静默删除文件(不报错) filedel('/path/to/file.txt'); ``` **filecopy() - 复制文件** ```php // 静默复制文件 filecopy('/source/file.txt', '/dest/file.txt'); ``` **filesave() - 保存文件** ```php // 保存文本到文件 $result = filesave('/path/to/file.txt', '文件内容'); ``` **fileload() - 读取文件** ```php // 读取文本文件 $content = fileload('/path/to/file.txt'); ``` **storsave() - 保存到存储目录** ```php // 保存到存储目录(支持日期变量) storsave('{Y}/{m}{d}/data.txt', '内容'); // 保存到:/web/ud/2024/0314/data.txt ``` **file_down() - 下载文件** ```php // 下载远程文件到本地 $path = file_down( 'https://example.com/image.jpg', // 远程URL '/ud/images/', // 保存路径 '', // 文件名(空则自动生成) 60, // 超时时间(秒) '.jpg' // 默认扩展名 ); ``` **file_ext() - 获取文件扩展名** ```php // 获取文件扩展名 $ext = file_ext('/path/to/file.txt'); // 返回 'txt' ``` **file_stor() - 获取文件的云存储URL** ```php // 获取文件访问URL(支持云存储) $url = file_stor('/2024/03/14/image.jpg'); // 返回完整的访问URL ``` **注意**:前端显示附件时,必须使用 `ciyfn.file_stor()` 将数据库存储的相对路径转换为云存储绝对URL。前端调用时: ```javascript var imgurl = ciyfn.file_stor('/2024/03/14/image.jpg'); // 返回完整的云存储URL(原始文件) var thumburl = ciyfn.file_stor('/2024/03/14/image.jpg', 'thumb'); // 返回缩略图云存储URL(图片资源用) ``` #### 配置和输入 **webini() - 读取配置文件** ```php // 读取web.ini配置 $config = webini('db'); // 返回:['host' => 'localhost', 'port' => '3306', ...] $config = webini('s3'); // 返回:['url' => 'https://...', ...] ``` **getstr()/getint()/post()/request()/cookie() - 获取输入** ```php // 获取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** ```php // 获取客户端真实IP(支持CDN、反向代理) $ip = getip(); // 返回'80.15.128.210' ``` #### 日志和调试 **savelogfile() - 保存日志到文件** ```php // 保存日志到文件 savelogfile('error', '错误信息'); // 保存到:/web/log/error.log // 包含请求信息 savelogfile('api', 'API调用', true); ``` **logdbstr() - 格式化数据变更** ```php // 格式化数据变更日志 $oldrow = ['id' => 1, 'name' => '旧名称', 'status' => 1]; $newrow = ['id' => 1, 'name' => '新名称', 'status' => 2]; $msg = logdbstr($oldrow, $newrow); // 返回:'Upd=1_|@|_name=旧名称→新名称_|@|_status=1→2' ``` **clog() - 调试输出** ```php // 调试输出变量 clog($variable); clog('Title', $variable); clog($var1, $var2, $var3); ``` #### 杂项工具 **randstr() - 生成随机字符串** ```php // 生成随机字符串 $str = randstr(10); // 生成10位随机字符串 $str = randstr(10, 'abcdef123456'); // 使用指定字符集 ``` **arrayrand() - 随机抽取数组元素** ```php // 随机抽取数组元素并删除 $arr = ['A', 'B', 'C', 'D', 'E']; $selected = arrayrand($arr, 2); // 返回 ['A', 'C'],原数组变为 ['B', 'D', 'E'] ``` **timems() - 获取微秒时间** ```php // 获取当前微秒数 $ms = timems(); // 返回如 1710385800123 ``` --- ### 常用函数索引 #### 用户相关 - `verifyfast()` - 快速用户验证 - `verifyuser()` - 用户验证 - `nopower()` - 权限验证 #### 配置相关 - `getconfig()` - 获取配置 - `setconfig()` - 设置配置 #### 日志相关 - `savelog()` - 保存日志 - `savelogdb()` - 数据库变更日志 #### 工具函数 - `tostamp()` - 转换为时间戳 - `todate()` - 时间戳转字符串 - `randstr()` - 生成随机字符串 - `getip()` - 获取IP地址 ### api json错误代码 | 代码 | 说明 | |------|------| | 1 | 成功 | | 0 | 失败 | | 2 | 未登录 | | 3 | 未授权 | | 9 | 函数未加载 | ### 开发环境配置 #### 数据库配置 (web.ini) ```ini [db] host = localhost port = 3306 database = c5_ciyon username = root password = wkxroot charset = utf8mb4 ``` --- ## 总结 Ciyon后台管理系统是一个功能完善、架构清晰的PHP开发框架。通过本文档,开发者可以快速理解系统的核心架构和开发规范,高效地进行二次开发和功能扩展。 **核心优势:** - 完整的用户认证和权限控制系统 - 安全的数据库操作层 - 灵活的模块化设计 - 完善的错误处理机制 - 丰富的工具类库 *文档版本:1.0* *技术支持:众产® https://ciy.cn/code*