ciyon_ai/aiskill/ciyon-后端API.md
2026-04-15 17:28:46 +08:00

1636 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
<?php
namespace web\admin;
class {功能名} {
public static function json_init() {
// 1. 验证用户登录
$rsuser = verifyfast();
// 2. 业务逻辑处理
// ...
// 3. 返回数据
return succjson($ret);
}
// 其他功能方法
public static function json_{方法名}() {
// ...
}
}
```
### 数据验证规范
```php
// 获取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');// 获取布尔值
```
### 错误处理规范
```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 文件
```
<?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
后端代码如下:
#### 查询列表接口
```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元=2000100为倍数默认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
<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)` 自动删除表格中的对应行
- 批量删除按钮使用 `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-36000代表未设置
$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*