初始化

master
wangxinglong 2022-02-22 17:27:27 +08:00
commit 31e9432ba0
5163 changed files with 636821 additions and 0 deletions

19
.env.bak Normal file
View File

@ -0,0 +1,19 @@
APP_DEBUG = true
APP_TRACE = true
[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = 211.149.245.223
DATABASE = dev_bee_cms
USERNAME = dev_bee_cms
PASSWORD = dT7yH5fmd28JG6ER
HOSTPORT = 3306
CHARSET = utf8mb4
DEBUG = true
PREFIX = bee_
[LANG]
default_lang = zh-cn

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
.env
/.idea
/.vscode
backup/data/*
/config/extra
*.log
runtime/*
storage/*
public/storage/*
.DS_Store
/Test.php
nginx.htaccess
dump.rdb
/app/controller/api/Test.php

32
LICENSE.txt Normal file
View File

@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

61
README.md Normal file
View File

@ -0,0 +1,61 @@
## 本CMS基于ThinkPHP 6.0开发
> - 运行环境要求 PHP7.4+
> - MySql版本限制 5.7+
<hr /><br />
### 功能列表 ###
> - [x] 后台菜单管理
> - [x] 后台权限管理
> - [x] 管理员管理
> - [x] 操作日志
> - [x] 配置文件配置
> - [x] 素材管理
> - [x] 内容管理
> - [x] 栏目管理
> - [x] 模型管理
> - [x] 配置管理
<hr /><br />
### 待办功能列表 ###
> - 迁移重构老版CMS功能
>> 碎片管理【block高度自定义配置】
> - 系统
> - 配置管理重构
>> 需求 配置优先查询配置文件,文件不存在则根据数据库生成配置文件
> - 内容管理重构
>> 拆分内容表为基础表,根据模型建表创建额外表
> - 模型管理重构
> - 相关SEO配置
> - 组图插件完善
>> 可排序、可自定义关联字段 如标题、url、alt、描述、等等关联可自由增减
<hr /><br />
### PHP扩展依赖列表 ###
> - fileinfo
> - exif 需要安装在mbsting扩展之后
> - gd
<hr /><br />
### PHP组件列表 ###
> - [图片处理 Intervention Image](http://image.intervention.io/getting_started/introduction)
> - [权限验证 think-authz](https://github.com/php-casbin/think-authz)
<hr /><br />
### 前端组件列表 ###
> - [xm-Select 下拉选择框](https://gitee.com/maplemei/xm-select)
> - [富文本编辑器选择 wangEditorv3.1.1](https://github.com/wangfupeng1988/wangEditor)
<hr /><br />

1
app/.htaccess Normal file
View File

@ -0,0 +1 @@
deny from all

666
app/common.php Normal file
View File

@ -0,0 +1,666 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 流年 <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\Collection;
use think\db\exception\DbException;
use think\exception\ClassNotFoundException;
use think\Model;
use think\Paginator;
// 应用公共文件
if (!function_exists('widget')) {
/**
* 渲染输出Widget
* @param string $name Widget名称
* @param array $data 传入的参数
* @return mixed
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function widget($name, $data = [])
{
return action($name, $data, 'widget');
}
}
if (!function_exists('action')) {
/**
* 调用模块的操作方法 参数格式 [模块/控制器/]操作
* @param string $url 调用地址
* @param string|array $vars 调用参数 支持字符串和数组
* @param string $layer 要调用的控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return mixed
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
{
$info = pathinfo($url);
$action = $info['basename'];
$module = '.' != $info['dirname'] ? $info['dirname'] : request()->controller();
$class = controller($module, $layer);
if (is_scalar($vars)) {
if (strpos($vars, '=')) {
parse_str($vars, $vars);
} else {
$vars = [$vars];
}
}
return app()->invokeMethod([$class, $action . config('route.action_suffix')], $vars);
}
}
if (!function_exists('controller')) {
/**
* 实例化(分层)控制器 格式:[模块名/]控制器名
* @access public
* @param string $name 资源地址
* @param string $layer 控制层名称
* @param bool $appendSuffix 是否添加类名后缀
* @param string $empty 空控制器名称
* @return object
* @throws ClassNotFoundException
*
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function controller($name, $layer = 'controller', $empty = '')
{
$class = parseClass($name, $layer);
if (class_exists($class)) {
return app()->make($class);
} elseif ($empty && class_exists($emptyClass = app()->parseClass($layer, $empty))) {
return app()->make($emptyClass);
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
}
if (!function_exists('parseClass')) {
/**
* 解析模块和类名
* @access protected
* @param string $name 资源地址
* @param string $layer 验证层名称
* @param bool $appendSuffix 是否添加类名后缀
* @return array
*
* milo 2019-05-08 从TP5.1代码中拿来修改的
*/
function parseClass($name, $layer)
{
if (false !== strpos($name, '\\')) {
$class = $name;
} else {
if (strpos($name, '/')) {
$names = explode('/', $name, 2);
$name = $names[1];
}
$class = app()->parseClass($layer, $name);
}
return $class;
}
}
if (!function_exists('randomStr')) {
/**
* 获取随机字符串
* @param int $type 0:数字(默认)1全部2:小写字母;3:大写字母4字母
* @param int $len 字符串长度
* @return string
*/
function randomStr($type = 0, $len = 5)
{
$strPol = "0123456789";
if ($type == 1) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ0123456789abcdefghijklmopqrstuvwyz";
} elseif ($type == 2) {
$strPol = "abcdefghijklmopqrstuvwyz";
} elseif ($type == 3) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ";
} elseif ($type == 4) {
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZabcdefghijklmopqrstuvwyz";
}
$max = strlen($strPol) - 1;
$str = '';
for ($i = 0; $i < $len; $i++) {
$str .= $strPol[rand(0, $max)];
}
return $str;
}
}
if (!function_exists('isMobile')) {
//判断访问终端是否为移动端
function isMobile()
{
// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
return true;
}
// 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
if (isset($_SERVER['HTTP_VIA'])) {
// 找不到为flase,否则为true
return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false;
}
// 脑残法,判断手机发送的客户端标志,兼容性有待提高。其中'MicroMessenger'是电脑微信
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$clientkeywords = [
'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips',
'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront',
'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc',
'midp', 'wap', 'mobile', 'MicroMessenger'
];
// 从HTTP_USER_AGENT中查找手机浏览器的关键字
if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
return true;
}
}
// 协议法,因为有可能不准确,放到最后判断
if (isset ($_SERVER['HTTP_ACCEPT'])) {
// 如果只支持wml并且不支持html那一定是移动设备
// 如果支持wml和html但是wml在html之前则是移动设备
if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'],
'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'],
'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
return true;
}
}
return false;
}
}
//根据栏目获取路径
if (!function_exists('getUri')) {
function getUri($category)
{
if (!empty($category) && isset($category['id'])) {
if (empty($category['route'])) {
return '/page/' . $category['id'] . ".html";
} else {
return $category['route'];
}
}
return '';
}
}
//根据文件大小转换为文字
if (!function_exists('sizeToStr')) {
function sizeToStr($size)
{
if (!is_numeric($size) || $size <= 0) {
return '';
}
$size = $size / 1024;
if ($size < 1024) {
return sprintf("%.2fK", $size);
}
$size = $size / 1024;
if ($size < 1024) {
return sprintf("%.2fM", $size);
}
$size = $size / 1024;
return sprintf("%.2fG", $size);
}
}
//根据路径获取文件名
if (!function_exists('getKeyByPath')) {
function getKeyByPath($path)
{
return substr($path, strrpos($path, '/') + 1);
}
}
//富文本中提取图片路径
if (!function_exists('getImageUrlFromText')) {
function getImageUrlFromText($content)
{
preg_match_all('/<img.*?src="(.*?)".*?>/is', $content, $imgs);
$data = [];
if (!empty($imgs) && !empty($imgs[1])) {
foreach ($imgs[1] as $img) {
if (substr($img, 0, 4) != 'http') {
$key = getKeyByPath($img);
$data[$key] = $img;
}
}
}
return $data;
}
}
//富文本中提取视频路径
if (!function_exists('getVideoUrlFromText')) {
function getVideoUrlFromText($content)
{
preg_match_all('/<video.*?src="(.*?)".*?>/is', $content, $videos);
$data = [];
if (!empty($videos) && !empty($videos[1])) {
foreach ($videos[1] as $video) {
if (substr($video, 0, 4) != 'http') {
$key = getKeyByPath($video);
$data[$key] = $video;
}
}
}
return $data;
}
}
//获取目录下的所有文件
if (!function_exists('getAllFilesByPath')) {
function getAllFilesByPath($path, $rootPath)
{
if (is_dir($path) && file_exists($path)) {
$items = scandir($path);
$files = [];
foreach ($items as $item) {
if (substr($item, 0, 1) != '.' && strpos($item, '_') == false) {
$itemPath = $path . '/' . $item;
if (is_file($itemPath)) {
$size = filesize($itemPath);
$files[$item] = [
'path' => str_replace($rootPath, '/', $itemPath),
'realPath' => $itemPath,
'size' => $size,
'sizeStr' => sizeToStr($size),
'suffix' => strtolower(substr($item, strrpos($item, '.') + 1)) //后缀
];
} elseif (is_dir($itemPath)) {
$childFiles = getAllFilesByPath($itemPath, $rootPath);
if (!empty($childFiles)) {
$files = array_merge($files, $childFiles);
} else {
rmdir($itemPath);
}
}
}
}
return $files;
}
return [];
}
}
//过滤get输入
if (!function_exists('getFilter')) {
function getFilter($value)
{
$getFilter = "'|(and|or)\b.+?(>|<|=|in|like)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if (is_array($value)) {
$forFilter = implode($value);
$forArray = true;
} else {
$forFilter = $value;
}
if (preg_match("/" . $getFilter . "/is", $forFilter) == 1) {
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
//过滤post录入
if (!function_exists('postFilter')) {
function postFilter($value)
{
$postFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if (is_array($value)) {
$forFilter = implode($value);
$forArray = true;
} else {
$forFilter = $value;
}
if (preg_match("/" . $postFilter . "/is", $forFilter) == 1) {
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
//过滤cookie数据
if (!function_exists('cookieFilter')) {
function cookieFilter($value)
{
$cookieFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
$forArray = false;
if (is_array($value)) {
$forFilter = implode($value);
$forArray = true;
} else {
$forFilter = $value;
}
if (preg_match("/" . $cookieFilter . "/is", $forFilter) == 1) {
$value = $forArray ? [] : '';
}
return filterExp($value);
}
}
if (!function_exists('filterExp')) {
function filterExp($value)
{
$filter = '/^(\s)+(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)(\s)+$/i';
$forArray = false;
if (is_array($value)) {
$forFilter = implode($value);
$forArray = true;
} else {
$forFilter = $value;
}
if (preg_match($filter, $forFilter) == 1) {
$value = $forArray ? [] : '';
}
return $value;
}
}
if (!function_exists('arrayHtmlFilter')) {
function arrayHtmlFilter($arr)
{
foreach ($arr as $k => $one) {
if (is_array($one)) {
$arr[$k] = arrayHtmlFilter($one);
} else {
$one = trim($one);
$arr[$k] = strip_tags($one);
}
}
return $arr;
}
}
if (!function_exists('toCamelString')) {
/**
* 转驼峰
*
* @param string $string
* @param false $small 默认false 为true首字母小写
* @return string
*/
function toCamelString(string $string, bool $small = false): string
{
//例: xxx_yYy_zzZ 和 xxX-yyy-Zzz
//1. 字符串转小写 xxx_yyy_zzz xxx-yyy-zzz
//2. -和_转空格 xxx yyy zzz
//3. 单词首字母大写 Xxx Yyy Zzz
//4. 清除空格 XxxYyyZzz
//处理下划线、减号 统统专程大驼峰 如xxx_yyy_zzz xxx-yyy-zzz 均转为 XxxYyyZzz
$string = strtolower($string);
$string = str_replace('-', ' ', $string);
$string = str_replace('_', ' ', $string);
$string = str_replace(' ', '', ucwords($string));
if ($small) {
$string = lcfirst($string);
}
return $string;
}
}
if (!function_exists('unCamelize')) {
/**
* 驼峰命名转特定分隔符[如下划线]命名
* @param string $camelCaps
* @param string $separator
* @return string
*/
function unCamelize(string $camelCaps, string $separator = '-'): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
}
}
if (!function_exists('generateRand')) {
/**
* 生成随机数
*
* @param int $length 长度
* @param string $type 模式 默认mix混合 number纯数字 alpha字母
* @return string
*/
function generateRand(int $length = 8, string $type = 'mix'): string
{
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$number = '0123456789';
$alphabetLen = strlen($alphabet) - 1;
$numberLen = 9;
switch ($type) {
case 'number':
$str = $number;
$len = $numberLen;
break;
case 'alpha':
$str = $alphabet;
$len = $alphabetLen;
break;
default:
$str = $alphabet . $number;
$len = $alphabetLen + $numberLen;
}
$randStr = '';
$str = str_shuffle($str);
for ($i = 0; $i < $length; $i++) {
$num = mt_rand(0, $len);
$randStr .= $str[$num];
}
return $randStr;
}
}
if (!function_exists('generateCode')) {
/**
* 生成特定编码
*
* @param string $type 类型 默认mix混合 number纯数字 alpha字母
* @param int $len 随机数长度
* @return string
*/
function generateCode(string $type = 'number', int $len = 6): string
{
//#时间戳+微秒+6位随机数
$time = microtime(true);
$timeStr = str_replace('.', '', $time);
return sprintf("%s%s", $timeStr, generateRand($len, $type));
}
}
if (!function_exists('checkMobile')) {
/**
* 检测手机号
*
* @param string $mobile
* @return bool
*/
function checkMobile(string $mobile): bool
{
if (preg_match("/^1[3456789]{1}\d{9}$/", $mobile)) {
return true;
} else {
return false;
}
}
}
if (!function_exists('arrayKeysFilter')) {
/**
* 数组键名过滤
*
* @param array $data
* @param array $allowKeys
* @return array
*/
function arrayKeysFilter(array $data, array $allowKeys): array
{
$list = [];
foreach ($data as $key => $val) {
if (in_array($key, $allowKeys)) {
$list[$key] = $val;
}
}
return $list;
}
}
if (!function_exists('getFilesize')) {
/**
* 尺寸单位转换
*
* @param $num
* @return string
*/
function getFilesize($num): string
{
$p = 0;
$format = 'B';
if ($num > 0 && $num < 1024) {
return number_format($num) . ' ' . $format;
}
if ($num >= 1024 && $num < pow(1024, 2)) {
$p = 1;
$format = 'KB';
}
if ($num >= pow(1024, 2) && $num < pow(1024, 3)) {
$p = 2;
$format = 'MB';
}
if ($num >= pow(1024, 3) && $num < pow(1024, 4)) {
$p = 3;
$format = 'GB';
}
if ($num >= pow(1024, 4) && $num < pow(1024, 5)) {
$p = 3;
$format = 'TB';
}
$num /= pow(1024, $p);
return number_format($num, 3) . ' ' . $format;
}
}
if (!function_exists('arrayNullToString')) {
/**
* 数组|或数据集中null值转为空字符串,并以数组格式返回
* 通常用于api json 返回内容null转换
*
* @param array $data 【array|collection】
* @return array
*/
function arrayNullToString($data)
{
if ($data instanceof Collection || $data instanceof Model) {
$data = $data->toArray();
}
// 判断是否可以遍历
if (is_iterable($data)) {
foreach ($data as $key => $val) {
if ($val instanceof Collection || $data instanceof Model) {
$val = $val->toArray();
}
if (is_iterable($val)) {
$data[$key] = arrayNullToString($val);
} elseif ($val === null) {
$data[$key] = '';
}
}
} else {
$data = [];
}
return $data;
}
}
if (!function_exists('arrayKeysExcludeFilter')) {
/**
* 数组键名排除过滤
*
* @param array $data
* @param array $excludeKeys 排除的字段
* @return array
*/
function arrayKeysExcludeFilter(array $data, array $excludeKeys): array
{
foreach ($data as $key => $val) {
if (in_array($key, $excludeKeys)) {
unset($data[$key]);
}
}
return $data;
}
}
if (!function_exists('getLatelyWeekDate')) {
/**
* 获取本周的周一 到周日的日期
*/
function getLatelyWeekDate()
{
//本周一
$oneDate = date('Y-m-d 00:00:00', (time() - ((date('w') == 0 ? 7 : date('w')) - 1) * 86400)); //w为星期几的数字形式,这里0为周日
$oneDateTime = strtotime($oneDate);
//返回周一到周天 1-7
return [
"1" => ["date"=>$oneDate],
"2" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 1)))],
"3" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 2)))],
"4" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 3)))],
"5" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 4)))],
"6" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 5)))],
"7" => ["date"=>date('Y-m-d 00:00:00', $oneDateTime + ((86400 * 6)))],
];
}
}
if (!function_exists('stringDesensitization')) {
/**
* 字符串脱敏 默认给手机号脱敏 其他未兼容
*
* @param string $string
* @param string $s
* @param int $start
* @param int $len
* @return string
*/
function stringDesensitization(string $string, string $s = '****', int $start = 3, int $len = 4): string
{
return substr_replace($string, $s, $start, $len);
}
}
if (!function_exists('checkPathExistWithMake')) {
/**
* 检测文件夹是否存在,不存在时自动创建(需要有写入权限)
* 支持递归创建
*
* @param string $absolutePath
* @return bool
*/
function checkPathExistWithMake(string $absolutePath): bool
{
try {
$absolutePath = rtrim($absolutePath, '/');
if (empty($absolutePath)) {
return false;
}
if (!is_dir($absolutePath)) {
return mkdir($absolutePath, 0777, true);
}
return true;
} catch (\Exception $e) {
return false;
}
}
}

85
app/controller/Base.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace app\controller;
use app\model\ArchivesCategory as ArchivesCategoryModel;
use app\model\Category;
use app\model\Link;
use app\model\System;
use app\repository\BlockRepository;
use app\repository\CmsRepository;
use think\facade\Cache;
use think\facade\Config;
/**
* 控制器基础类
*/
class Base extends BaseController
{
//需要向模板传递的值
protected $data = [
"activeCategoryId"=> 0,//当前选中的顶级栏目
"links"=> [],//友情连接
"article"=> [],//文章
"blocks"=> [],//碎片
];
//系统配置信息
protected $system = [];
protected $auth = [];
protected $authId = 0;
protected $aboutCategory = [];
// 初始化
protected function initialize()
{
$this->auth = session('frontend_auth') ?? [];
$this->data['auth'] = $this->auth;
$this->authId = $this->auth['id'] ?? 0;
//加载基础配置
Config::load('extra/base', 'extra_system');
$this->system = config("extra_system");
$this->data['system'] = $this->system;
$this->setSeo();
$this->setLinks();
$this->setActiveCategory(ArchivesCategoryModel::index_id);
}
//设置选中的栏目
protected function setActiveCategory($id)
{
$this->data["active_category_id"] = $id;
}
//设置友情链接 缓存1小时
protected function setLinks()
{
if(Cache::has("links")){
$this->data["links"] = Cache::get("links",[]);
}else{
$links = Link::findList([],[],1,20,null,["sort"=>"asc"]);
if(isset( $links['list'])){
$this->data["links"] = $links['list']->toArray();
}else{
$this->data["links"] = [];
}
Cache::set("links",$this->data["links"],3600);
}
}
//设置SEO信息
protected function setSeo($title = '', $keywords = '', $description = '')
{
$this->data['seoTitle'] = $title ?: $this->system['seo_title'] ?? '';
$this->data['seoKeywords'] = $keywords ?: $this->system['seo_keywords'] ?? '';
$this->data['seoDescription'] = $description ?: $this->system['seo_description'] ?? '';
}
//模板
protected function view($template = '')
{
return view($template)->assign($this->data);
}
}

View File

@ -0,0 +1,254 @@
<?php
declare (strict_types=1);
namespace app\controller;
use think\{App, Request, response\Json, response\Redirect, response\View, Validate};
use Exception;
use think\exception\ValidateException;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* 无需登录的方法,同时也就不需要鉴权了
*
* @var array
*/
protected $noNeedLogin = [];
/**
* 无需鉴权的方法,但需要登录
*
* @var array
*/
protected $noNeedRight = [];
/**
* Request实例
* @var Request
*/
protected $request;
/**
* 应用实例
* @var App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
list($validate, $scene) = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
/**
* 验证器
*
* @param array $data
* @param $validate
* @param array $message
* @param bool $batch
* @return Json|bool
* @throws Exception
*/
protected function validateByApi(array $data, $validate, array $message = [], bool $batch = false)
{
try {
$this->validate($data, $validate, $message, $batch);
return true;
} catch (ValidateException $e) {
$msg = $e->getMessage();
if ($batch) {
$msg = implode(',', $e->getError());
}
return $this->json(4000, $msg);
} catch (Exception $e) {
throw $e;
}
}
/**
* 验证器
*
* @param array $data
* @param $validate
* @param array $message
* @param bool $batch
* @return Redirect
* @throws Exception
*/
protected function validateByView(array $data, $validate, array $message = [], bool $batch = false): Redirect
{
try {
$this->validate($data, $validate, $message, $batch);
} catch (ValidateException $e) {
$msg = $e->getMessage();
if ($batch) {
$msg = implode(',', $e->getError());
}
return $this->error( $msg);
} catch (Exception $e) {
throw $e;
}
}
/**
* 操作成功跳转的快捷方法
* @access protected
* @param mixed $msg 提示信息
* @param string|null $url 跳转的URL地址
* @param mixed $data 返回的数据
* @param integer $wait 跳转等待时间
* @param array $header 发送的Header信息
* @return Redirect
*/
protected function success($msg = '', string $url = null, $data = '', int $wait = 3, array $header = []): Redirect
{
if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) {
$url = $_SERVER["HTTP_REFERER"];
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 1,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('error/404', $result));
}
/**
* 操作错误跳转的快捷方法
* @access protected
* @param mixed $msg 提示信息
* @param string|null $url 跳转的URL地址
* @param mixed $data 返回的数据
* @param integer $wait 跳转等待时间
* @return Redirect
*/
protected function error($msg = '', string $url = null, $data = '', int $wait = 3): Redirect
{
if (is_null($url)) {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = $this->request->isAjax() ? '' : '/';
} else {
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
}
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 0,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('error/404', $result));
}
/**
* 返回封装后的API数据到客户端
* 以json格式抛出异常
* @access protected
* @param integer $code 返回的code
* @param mixed $msg 提示信息
* @param mixed $data 要返回的数据
* @return Json
*/
protected function json(int $code = 0, $msg = '操作成功', $data = []): Json
{
$result = [
'code' => $code,
'msg' => $msg,
'data' => $data
];
return json($result);
}
/**
* URL重定向
* @access protected
* @param string $url 跳转的URL表达式
* @return Redirect
*/
protected function redirect($url): Redirect
{
if (!is_string($url)) {
$url = $url->__toString();
}
return redirect($url);
}
}

48
app/controller/Error.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace app\controller;
class Error extends BaseController
{
public function __call($method, $args)
{
if(request()->isAjax()) {
return $this->json(4004, 'error request!');
} else {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = '/';
} else {
$domain = $this->request->domain();
$urlInfo = parse_url($referer);
$scheme = $urlInfo['scheme'] ?? '';
$requestSrc = '';
if (!empty($scheme)) {
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
}
if($domain != $requestSrc) {
$url = '/';
} else {
$url = 'javascript:history.back(-1);';
}
}
$result = [
'code' => 404,
'msg' => '无效请求! 没有找到相关资源',
'data' => [],
'url' => $url,
'wait' => 5,
];
return view('/manager/error/jump')->assign($result);
}
}
public function jump()
{
$param = request()->param();
return view()->assign($param);
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace app\controller;
use app\model\Feedback as FeedbackModel;
use app\repository\AccountRepository;
use think\captcha\facade\Captcha;
/**
* 留言与意见反馈
*
* Class Feedback
* @package app\controller
*/
class Feedback extends Base
{
/**
* 用户提交反馈信息
*/
public function submitFeedback()
{
if (!$this->request->isPost()) {
return $this->json(4001, '请求方式错误!');
}
$params = [
'user_name' => $this->request->post('user_name/s', ''),
'user_tel' => $this->request->post('user_tel/s', ''),
'user_email' => $this->request->post('user_email/s', ''),
'content' => $this->request->post('content/s', ''),
'code' => $this->request->post('code/s', ''),
];
try{
$validate = $this->validateByApi($params, [
'code|验证码'=>'require|captcha',
'user_name|姓名' => 'chs|min:2|max:30',
'user_tel|联系电话' => 'min:5|max:20|mobile',
'user_email|邮箱地址' => 'email',
'content|留言内容' => 'require|min:6|max:500',
]);
if ($validate !== true) {
return $validate;
}
if ($this->authId > 0) {
$account = AccountRepository::getInstance()->findById($this->authId);
if ($account) {
$params['user_name'] = $params['user_name'] ?: ($account->nickname ?? '');
$params['user_tel'] = $params['user_tel'] ?: ($account->mobile ?? '');
}
}
FeedbackModel::create([
'account_id' => $this->authId,
'user_name' => $params['user_name'] ?: '',
'user_tel' => $params['user_tel'] ?: '',
'user_email' => $params['user_email'],
'content' => $params['content'],
'created_at' => date('Y-m-d H:i:s'),
]);
return $this->json(0, '感谢你的留言!');
} catch (\Exception $e) {
return $this->json(5001, '服务器异常,留言信息提交失败!');
}
}
public function code()
{
return Captcha::create('verify');
}
}

39
app/controller/Index.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace app\controller;
use app\exception\RepositoryException;
use app\model\Archives;
use app\model\ArchivesCategory;
use app\model\Block;
use app\model\Slide;
use app\model\SlidePosition;
use Exception;
use think\response\Redirect;
/**
* auth 王兴龙 2022-02-18
* */
class Index extends Base
{
/**
* @return Redirect
* @throws Exception
*/
public function index()
{
try {
//banner
$this->data['slide'] = Slide::findList([["position","=",SlidePosition::home_position]])['list'];
$this->data['topCategoryId'] = ArchivesCategory::index_id ;
//碎片
$this->data['blocks'] = Block:: getByCategoryId(ArchivesCategory::index_id);
return $this->view();
}catch (RepositoryException $e){
return $this->error("服务器错误");
}
}
}

150
app/controller/Page.php Normal file
View File

@ -0,0 +1,150 @@
<?php
namespace app\controller;
use app\model\Archives;
use app\model\ArchivesCategory as ArchivesCategoryModel;
use app\model\ArchivesModel;
use app\model\Block;
use app\service\DxtcPage;
use Exception;
use think\Paginator;
use think\response\Redirect;
use think\response\View;
/**
* auth 王兴龙 2022-02-18
* */
class Page extends Base
{
/**
* @return Redirect
* @throws Exception
*/
public function index()
{
$categoryId = input("categoryId/d",0);
if($categoryId==ArchivesCategoryModel::index_id){
return $this->redirect("/");
}
$category = ArchivesCategoryModel::findById($categoryId);
if(empty($category)){
return $this->error("内容不存在");
}
//如果有链接
if(!empty($category['link'])){
return $this->redirect($category['link']);
}
$this->data["category"] = $category;
$this->setActiveCategory($category['id']);
$this->data['topCategoryId'] = ArchivesCategoryModel::firstGradeById($category['id']) ;
$categoryModel = ArchivesModel::allModel();
//所有类型都要把碎片加上
$this->data["blocks"] = Block:: getByCategoryId($category['id']);
switch ($category['model_id']){
//文章模型
case $categoryModel[ArchivesModel::MODEL_ARCHIVES]:
return $this->archives($category,$category['cover_template']);
break;
default:
return $this->redirect("/");
}
}
//文章列表
/**
*
* @param $categoryId 栏目id
* @param $categoryTemplate 模板
*/
protected function archives($category,$categoryTemplate)
{
//动态设置当前分页驱动
app('think\App')->bind(Paginator::class, DxtcPage::class);
$this->data["archives"] = Archives::getListPageByCategory($category['id'],$category['page_size']);
return $this->view(empty($categoryTemplate)?"archives_default":$categoryTemplate);
}
/**
*
* @param $categoryId 栏目id
* @param $categoryTemplate 模板
*/
protected function page($categoryTemplate)
{
return $this->view(empty($categoryTemplate)?"page_default":$categoryTemplate);
}
/**
* 文章详情
*
* @param $articleId
*/
public function archivesInfo()
{
$articleId = input("articleId/d",0);
$archive = Archives::findOne([["id","=",$articleId]]);
if(empty($archive)){
return $this->error("内容不存在");
}
$archive->inc("views")->update();
$this->data["archive"] = $archive;
$category = ArchivesCategoryModel::findById($archive['category_id']);
if(empty($category)){
return $this->error("内容不存在");
}
$this->data["category"] = $category;
$this->setActiveCategory($category['id']);
$this->data['topCategoryId'] = ArchivesCategoryModel::firstGradeById($category['id']) ;
//所有类型都要把碎片加上
$this->data["blocks"] = Block:: getByCategoryId($category['id']);
$seo_title = empty($archive['seo_title'])
?
$archive['title']
:
$archive['seo_title'];
$seo_keywords = empty($archive['seo_keywords'])
?
""
:
$archive['seo_keywords'];
$seo_description = empty($archive['seo_description'])
?
""
:
$archive['seo_description'];
$this->setSeo( $seo_title,$seo_keywords,$seo_description);
// 上一篇
$this->data["prev"] = Archives::findOne([["category_id","=",$category['id']], ['sort', '<', $archive['sort']]]
, [], function ($q) {
return $q->with(["archivesCategory"])->order(['sort'=> 'desc']);
});
// 下一篇
$this->data["next"] = Archives::findOne([["category_id","=",$category['id']], ['sort', '>', $archive['sort']]]
, [], function ($q) {
return $q->with(["archivesCategory"])->order(['sort'=> 'asc']);
});
return $this->view(!empty($category['detail_template'])?$category['detail_template']:"archives_default");
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace app\controller\api;
use app\model\AccountRecord;
use app\model\Disease;
use app\repository\ArchivesRepository;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
use app\exception\RepositoryException;
class Archives extends Base
{
protected $noNeedLogin = [];
}

View File

@ -0,0 +1,30 @@
<?php
namespace app\controller\api;
use app\controller\BaseController;
/**
* API控制器基础类
*/
class Base extends BaseController
{
// 布尔值数字关系
public const BOOL_FALSE = 0;
public const BOOL_TRUE = 1;
protected function initialize()
{
parent::initialize();
$this->middleware = [
'jwt',
'apiLogin' => ['except' => $this->noNeedLogin]
];
}
public function __call($method, $args)
{
return $this->json(4004, 'error request!');
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace app\controller\api;
use app\exception\RepositoryException;
use app\repository\AccountRepository;
use app\repository\CommonRepository;
use app\repository\OperationRepository;
use app\repository\OrderRepository;
use app\service\Sms;
use app\validate\CommonValidate;
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
class Common extends Base
{
protected $noNeedLogin = [
'slidePositions',
'slides',
];
/**
* 发送短信验证码
*
* @OA\Post(
* path="/common/send-code",
* tags={"common"},
* operationId="sendCode",
* )
*/
public function sendCode(): Json
{
$input = input('post.');
$validate = new CommonValidate();
if (!$validate->scene('send_sms')->check($input)) {
return $this->json(4001, '参数错误');
}
if (!in_array($input['type'], ['register', 'login', 'binding'])) {
return $this->json(4002, '参数错误');
}
CommonRepository::getInstance()->sendSms($input['phone'], $input['type']);
return $this->json();
}
/**
* 查看轮播图可视位置配置信息
*/
public function slidePositions(): Json
{
$repo = OperationRepository::getInstance();
$list = $repo->slidePositions();
return $this->json(0, 'success', $list);
}
/**
* 轮播图
*/
public function slides(): Json
{
$size = $this->request->param('size/d', 0);
$position = trim($this->request->param('position/s', ''));
if (empty($position)) {
return $this->json();
}
try {
$repo = OperationRepository::getInstance();
$list = $repo->slideListByPosition($position, $size);
} catch (\Exception $e) {
$list = new Collection();
}
return $this->json(0, 'success', $list);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace app\controller\api;
use app\exception\RepositoryException;
use app\job\NotifySms;
use app\model\AccountFootmarks;
use app\model\HotKeyword;
use app\repository\AccountRepository;
use app\service\ExtraConfig;
use think\Collection;
use think\facade\Config as CConfig;
use think\facade\Queue;
use think\response\Json;
class Index extends Base
{
protected $noNeedLogin = [];
public function index(): Json
{
return json(['code' => 0, 'msg' => 'I am index']);
}
/**
* 测试用
*
* @return Json
* @throws RepositoryException
*/
public function test(): Json
{
$userId = $this->request->middleware('userInfo')['user_id'] ?? 0;
$user = AccountRepository::getInstance()->info($userId, []);
return json(['code' => 0, 'msg' => 'I am test ', 'data' => $user]);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace app\controller\api;
class User extends Base
{
protected $noNeedLogin = [];
public function index()
{
return json(['code' => 0, 'msg' => 'I am index']);
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace app\controller\api\file;
use app\controller\api\Base;
use app\model\File;
use app\model\System;
use app\service\Image;
use app\validate\Upload as VUpload;
use think\facade\Config;
use think\facade\Filesystem;
use think\facade\Lang;
use think\response\Json;
/**
* 文件上传
*
* Class Upload
* @package app\controller\api\file
*/
class Upload extends Base
{
protected $noNeedLogin = [];
// 图片上传是否进行压缩[max-width:1920px]
private bool $isCompress = true;
private $validate = null;
// 文件上传对外默认保存目录(相对路径)
private string $uploadPath = '';
// 文件上传对外默认保存目录是否有写权限
private bool $uploadPathIsWritable = false;
protected bool $saveToOos = false;
public function initialize()
{
parent::initialize();
$system = System::getSystem();
if (!empty($system)) {
$this->isCompress = $system['compress'] ?? true;
}
$this->validate = new VUpload();
$this->uploadPath = Config::get('filesystem.disks.local.url');
if(is_writable(app()->getRootPath() . 'public' . $this->uploadPath)){
$this->uploadPathIsWritable = true;
}
$this->cancelTimeLimit();
}
/**
* 通用文件上传
* @return Json
*/
public function file()
{
$file = request()->file('file');
if (empty($file)) {
return $this->json(4001, '请上传的文件');
}
if($this->validate->checkFile($file)){
try{
if(!$this->uploadPathIsWritable){
throw new \Exception('上传文件夹需要写入权限');
}
$src = Filesystem::putFile('files/'.date('Ym'), $file, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$return['src'] = $src;
$return['name'] = $file->getOriginalName();
//加入上传文件表
File::add($file, $src, $file->md5());
} catch (\Exception $e) {
return $this->json(4003, $e->getMessage());
}
return $this->json(0,'success', $return);
}else{
$errorMsg = Lang::get($this->validate->getError());
return $this->json(4002, $errorMsg);
}
}
/**
* 通用图片上传
* @return Json
*/
public function image()
{
$image = request()->file('image');
if (empty($image)) {
return $this->json(4001, '请上传图片文件');
}
$md5 = $image->md5();//文件md5
if($this->validate->checkImage($image)){
try{
if(!$this->uploadPathIsWritable){
throw new \Exception('上传文件夹需要写入权限');
}
$src = Filesystem::putFile('images/'.date('Ym'), $image, 'uniqid');
$src = $this->uploadPath . '/' . $src;
$return['src'] = $src;
if($this->isCompress){
Image::resize($src);
}
//加入上传文件表
File::add($image, $src,$md5);
} catch (\Exception $e) {
return $this->json(4003, $e->getMessage());
}
return $this->json(0, 'success', $return);
}else{
$errorMsg = Lang::get($this->validate->getError());
return $this->json(4002, $errorMsg);
}
}
/**
* 同步到OOS服务器存储
* @param string $src
*/
private function syncToOos(string $src)
{
}
}

View File

@ -0,0 +1,736 @@
<?php
namespace app\controller\manager;
use app\exception\RepositoryException;
use app\model\AccountDataLog;
use app\model\AccountDataOperationLog;
use app\model\AccountLevel;
use app\model\AccountOperateLog;
use app\model\AccountTagPivot;
use app\model\CategoryConfig as CategoryConfigModel;
use app\model\CustomerReceive;
use app\model\AccountTag;
use app\model\Account as AccountModel;
use app\model\Order;
use app\model\Staff;
use app\model\Activity;
use app\repository\AccountRepository;
use app\repository\OrderRepository;
use app\service\Math;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Log;
use think\response\Json;
use think\response\View;
/**
* 用户管理
*
* Class Footmarks
* @package app\controller\manager
*/
class Account extends Base
{
protected $noNeedLogin = ['getStaffList', 'getAccountList'];
/**
* 详情
*
* @return View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws RepositoryException
*/
public function detail(): View
{
$id = input('id/d', 0);
$item = AccountRepository::getInstance()->findById($id, [], function ($q) {
return $q->with(['serviceList']);
});
$statusList = [
Order::STATUS_SHIPPED, Order::STATUS_PAID, Order::STATUS_COMPLETED
];
$consumption = OrderRepository::getInstance()->userOrderList($id, [], 1, 0, $statusList);
$orderNum = 0;
$orderScoreNum = 0;
$totalPrice = 0;
$totalScore = 0;
$totalCoin = 0;
$consumption->each(function ($item) use (&$totalPrice, &$totalScore, &$totalCoin, &$orderScoreNum, &$orderNum) {
if ($item->is_score == AccountModel::COMMON_ON) {
$orderScoreNum += 1;
} else {
$orderNum += 1;
}
$totalPrice += $item->price;
$totalScore += $item->score;
$totalCoin += $item->coin;
});
$item['total_price'] = Math::fen2Yuan($totalPrice);
$item['total_score'] = $totalScore;
$item['total_coin'] = $totalCoin;
$item['order_num'] = $orderNum;
$item['order_score_num'] = $orderScoreNum;
$item['order_newest'] = $consumption->toArray()[0] ?? [];
$item['customer_service'] = $item->serviceList->name ?? '';
$item['source_text'] = AccountRepository::getInstance()->getSourceDetail($id);
$item['channel_text'] = AccountModel::channelTextList()[$item['channel']] ?? '';
$this->data['item'] = $item;
return $this->view();
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = AccountRepository::getInstance()->findById($id)) {
if ($this->request->isPost()) {
return $this->json(4000, '用户不存在');
} else {
return $this->error('用户不存在');
}
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'nickname' => 'require',
]);
if ($validate !== true) {
return $validate;
}
try {
$info->save($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = AccountModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$size = input('size/d', 20);
$searchParams = input('searchParams');
$search = [];
$other = [];
if ($searchParams) {
foreach ($searchParams as $key => $param) {
if ($key == 'tag' && !empty($param)) {
$other['tag_id'] = $param;
continue;
}
if ($param || $param == '0') {
$search[] = [$key, 'like', '%'.$param.'%'];
}
}
}
$search[] = ['phone_active', '=', AccountModel::COMMON_ON];
// 后台绑定的账号
$accountId = $this->auth['account_id'] ?? 0;
try {
$items = AccountRepository::getInstance()->customerList($search, [], $accountId, $page, $size, function ($q) use ($other) {
return $q->when(isset($other['tag_id']), function ($query) use ($other) {
$query->leftJoin('account_tag_pivot atp', 'atp.account_id = id')->where('atp.tag_id', $other['tag_id']);
});
});
return $this->json(0, '操作成功', $items);
} catch (RepositoryException $e) {
return $this->json(4001, $e->getMessage());
} catch (Exception $e) {
return $this->json(5001, '获取用户列表失败'.$e->getMessage());
}
}
$this->data['channelList'] = AccountModel::channelTextList();
$this->data['customerList'] = Staff::getCustomerServiceList();
$this->data['tagList'] = AccountTag::getTags();
return $this->view();
}
/**
* 分配员工
*
* @return View|Json
* @throws Exception
*/
public function staff()
{
$id = input('id/s', '');
if ($this->request->isPost()) {
$ids = input('ids/s');
$staffId = input('staff_id/d', 0);
if (empty($ids)) {
return $this->json(4001, '请选择要操作的用户');
}
if (!$staffId) {
return $this->json(4001, '请选择分配的员工');
}
$ids = explode(',', $ids);
try {
CustomerReceive::allotServiceByBatch($ids, $staffId);
return $this->json(0, '操作成功');
} catch (RepositoryException $e) {
return $this->json(4001, $e->getMessage());
} catch (Exception $e) {
Log::error('分配指定员工失败'.$e->getMessage());
return $this->json(5001, '分配指定员工失败');
}
}
$staffList = Staff::getCustomerServiceList();
// 分配的是客服分组下所有客服
$this->data['servicerList'] = json_encode($staffList, JSON_UNESCAPED_UNICODE);
$this->data['id'] = $id;
return $this->view();
}
/**
* 分配客户标签
*
* @return View|Json
* @throws Exception
*/
public function tag()
{
$id = input('id/s', '');
if ($this->request->isPost()) {
$ids = input('ids/s');
$tagId = input('tag_id/s');
if (empty($ids)) {
return $this->json(4001, '请选择要操作的用户');
}
if (empty($tagId)) {
return $this->json(4001, '请选择分配的标签');
}
$ids = explode(',', $ids);
$tags = explode(',', $tagId);
Db::startTrans();
try {
// 删除所有人标签
AccountTagPivot::whereIn('account_id', $ids)->delete();
// 新增标签
$insert = [];
foreach ($ids as $id) {
foreach ($tags as $tag) {
$arr = [];
$arr['account_id'] = $id;
$arr['tag_id'] = $tag;
$insert[] = $arr;
}
}
(new AccountTagPivot())->saveAll($insert);
Db::commit();
return $this->json(0, '操作成功');
} catch (RepositoryException $e) {
Db::rollback();
return $this->json(4001, $e->getMessage());
} catch (Exception $e) {
Db::rollback();
Log::error('分配客户标签失败'.$e->getMessage());
return $this->json(5001, '分配客户标签失败');
}
}
$tagList = AccountTag::order('sort', 'desc')->order('id', 'asc')->select()->toArray();
// 分配的是线上客服
$this->data['tagList'] = json_encode($tagList, JSON_UNESCAPED_UNICODE);
$this->data['id'] = $id;
return $this->view();
}
/**
* 分配客户来源
*
* @return View|Json
* @throws Exception
*/
public function source()
{
$id = input('id/s', '');
if ($this->request->isPost()) {
$ids = input('ids/s');
$channel = input('channel/s');
if (empty($ids)) {
return $this->json(4001, '请选择要操作的用户');
}
if (empty($channel)) {
return $this->json(4001, '请选择分配的客户来源');
}
$value = $channel == AccountModel::CHANNEL_MEMBER ? input('type_staff') : input('type_'.$channel);
$ids = explode(',', $ids);
Db::startTrans();
try {
$field = 'id,channel,inviter_account_id,inviter_parent_id,source_code';
$accountList = AccountModel::whereIn('id', $ids)->column($field, 'id');
$update = [];//更新account表
$insert = [];//插入account_operate_log表
switch ($channel) {
case AccountModel::CHANNEL_NORMAL:
// 设为自然流量 清空上级邀请人和上上级邀请人
$update = ['inviter_account_id' => 0, 'inviter_parent_id' => 0, 'channel' => $channel, 'source_code' => null];
break;
case AccountModel::CHANNEL_CUSTOMER:
case AccountModel::CHANNEL_MEMBER:
// 客户分享或员工分享 修改上级邀请人ID
$update = ['inviter_account_id' => $value, 'channel' => $channel, 'source_code' => null];
break;
case AccountModel::CHANNEL_ACTIVITY:
// 活码分享
$update = ['inviter_account_id' => 0, 'source_code' => $value, 'channel' => $channel];
break;
}
$now = date('Y-m-d H:i:s');
$createdBy = $this->auth['user_id'] ?? 0;
foreach ($ids as $id) {
$log = [];
$originalSource = $accountList[$id] ?? [];
switch ($channel) {
case AccountModel::CHANNEL_NORMAL:
$log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】",
$originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0,
$originalSource['source_code'] ?? '', $channel, 0, null);
break;
case AccountModel::CHANNEL_CUSTOMER:
case AccountModel::CHANNEL_MEMBER:
$log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】",
$originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0,
$originalSource['source_code'] ?? '', $channel, $value, null);
break;
case AccountModel::CHANNEL_ACTIVITY:
$log['description'] = sprintf("从原始来源【channel=%s,inviter_account_id=%s,source_code=%s】变更为新来源【channel=%s,inviter_account_id=%s,source_code=%s】",
$originalSource['channel'] ?? '', $originalSource['inviter_account_id'] ?? 0,
$originalSource['source_code'] ?? '', $channel, 0, $value);
break;
}
$log['type'] = AccountOperateLog::TYPE_CHANGE_SOURCE;
$log['account_id'] = $id;
$log['created_at'] = $now;
$log['created_by'] = $createdBy;
$insert[] = $log;
}
(new AccountOperateLog())->saveAll($insert);
(new AccountModel())->whereIn('id', $ids)->save($update);
Db::commit();
return $this->json(0, '操作成功');
} catch (RepositoryException $e) {
Db::rollback();
return $this->json(4001, $e->getMessage());
} catch (Exception $e) {
Db::rollback();
Log::error('分配客户标签失败'.$e->getMessage());
return $this->json(5001, '分配客户标签失败');
}
}
// 客服来源
$this->data['channelList'] = AccountModel::channelTextList();
$this->data['id'] = $id;
return $this->view();
}
/**
* 到店设置
*
* @return View|Json
* @throws Exception
*/
public function sign()
{
$id = input('id/s', '');
if ($this->request->isPost()) {
$ids = input('ids/s');
$sign = input('sign/d');
if (empty($ids)) {
return $this->json(4001, '请选择要操作的用户');
}
if (!in_array($sign, [AccountModel::COMMON_ON, AccountModel::COMMON_OFF])) {
return $this->json(4001, '请选择是否到店');
}
$ids = explode(',', $ids);
Db::startTrans();
try {
(new AccountModel())->whereIn('id', $ids)->save(['is_sign' => $sign]);
Db::commit();
return $this->json(0, '操作成功');
} catch (Exception $e) {
Db::rollback();
Log::error('是否到店操作失败'.$e->getMessage());
return $this->json(5001, '是否到店操作失败');
}
}
$this->data['id'] = $id;
return $this->view();
}
/**
* 获取员工列表
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException|Exception
*/
public function getStaffList(): Json
{
if ($this->request->isPost()) {
$keyword = input('keyword/s', '');
$type = input('type/s', '');//员工类型标识 如在线客服=customer-online
$page = input('page/d', 1);
$size = input('size/d', 0);
$id = input('id/d', 0);
$relationIds = [];//已选记录
if ($id > 0 && $activity = Activity::findById($id)) {
$relationIds = explode(',', $activity['account_id']);
}
$res = Staff::getStaff($type, $keyword, $page, $size);
if ($res['total'] > 0) {
$res['list'] = $res['list']->toArray();
foreach ($res['list'] as &$item) {
if (count($relationIds) > 0 && in_array($item['id'], $relationIds)) {
$item['selected'] = true;
}
}
}
return $this->json(0, '操作成功', $res);
}
return $this->json(4001, '非法请求');
}
/**
* 获取客户列表
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException|Exception
*/
public function getAccountList(): Json
{
if ($this->request->isPost()) {
$keyword = input('keyword/s', '');
$page = input('page/d', 1);
$size = input('size/d', 10);
$id = input('id', '');
$relationIds = explode(',', $id);//已选记录
$where = [];
$where[] = ['is_staff', '=', AccountModel::COMMON_OFF];
if (!empty($keyword)) {
$where[] = ['nickname|real_name|mobile', 'like', '%'.$keyword.'%'];
}
$res = AccountModel::findList($where, ['id', 'nickname', 'real_name', 'mobile'], $page, $size);
if ($res['total'] > 0 && $relationIds) {
$res['list'] = $res['list']->toArray();
foreach ($res['list'] as &$item) {
$item['name_text'] = sprintf("昵称:%s;真实姓名:%s,手机号:%s", $item['nickname'], $item['real_name'], $item['mobile']);
if (count($relationIds) > 0 && in_array($item['id'], $relationIds)) {
$item['selected'] = true;
}
}
}
return $this->json(0, '操作成功', $res);
}
return $this->json(4001, '非法请求');
}
/**
* 充值孔雀币
* */
public function rechargeCoin()
{
$id = input('id/s');
if ($this->request->isPost()) {
$ids = input('ids/s');
if (empty($ids)) {
return $this->json("4003", "请选择用户");
}
$ids = explode(",", $ids);
if (count($ids) > 1000) {
return $this->json("4003", "一次最多选择1000条");
}
$coin = input("coin/d", 1, "abs");
$Account = AccountRepository::getInstance()->getModel()
->where("id", "in", $ids)->lock(true)
->select();
Db::startTrans();
try {
AccountRepository::getInstance()->getModel()->where("id", "in", $ids)->inc("coin", $coin)->update();
$time = date("Y-m-d H:i:s");
$dataLog = [];
$dataOperationLog = [];
$Account->each(function ($item) use ($coin, $time, &$dataLog, &$dataOperationLog) {
$dataLog[] = [
"account_id" => $item["id"],
"operator" => ($this->auth['nickname'] ?? ""),
"operator_id" => $this->auth['user_id'] ?? 0,
"name" => "后台充值孔雀币",
"num" => $coin,
"type" => AccountDataLog::TYPE_COIN,
"action" => AccountDataLog::ACTION_ADMIN_RECHARGE,
"created_at" => $time,
"surplus" => ($item["coin"] + $coin),
];
$dataOperationLog[] = [
"account_id" => $item["id"],
"operator" => ($this->auth['nickname'] ?? ""),
"num" => $coin,
"remark" => "后台充值孔雀币",
"type" => AccountDataLog::TYPE_COIN,
"created_at" => $time,
];
});
AccountDataLog::insertAll($dataLog);
AccountDataOperationLog::insertAll($dataOperationLog);
Db::commit();
return $this->json();
} catch (Exception $e) {
Db::rollback();
return $this->json("5003", "充值失败-1:".$e->getMessage());
} catch (RepositoryException $e) {
Db::rollback();
return $this->json("5003", "充值失败-2:".$e->getMessage());
}
}
$this->data['id'] = $id;
return $this->view();
}
/**
* 用户等级管理
* */
public function accountLevel()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 10);
$items = AccountLevel::findList([], [], $page, $limit, null, ["value" => "desc"]);
$items["list"]->each(function (&$item) {
if (empty($item["rights"])) {
return;
}
$str = "";
foreach (explode(",", $item["rights"]) as $ritem) {
foreach (AccountLevel::$rightsArray as $mitem) {
if ($mitem["key"] == $ritem) {
$str .= $mitem["title"]." ";
}
}
}
$item->rights = $str;
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
/**
* 添加用户等级管理
* */
public function addAccountLevel()
{
if ($this->request->isPost()) {
$item = input('post.');
$rule = [
'name|名称' => 'require',
'value|成长值' => 'require',
];
$validate = $this->validateByApi($item, $rule);
if ($validate !== true) {
return $validate;
}
try {
AccountLevel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
//权益列表
$rightsArray = AccountLevel::$rightsArray;
$this->data['rightsJson'] = json_encode($rightsArray);
return $this->view();
}
/**
* 添加用户等级管理
* */
public function delAccountLevel()
{
if ($this->request->isPost()) {
$id = input('id/d');
$item = AccountLevel::findById($id);
if (empty($item)) {
return $this->json(4001, "信息不存在");
}
AccountLevel::destroy($id);
return $this->json();
}
}
/**
* 编辑用户等级管理
* */
public function editAccountLevel()
{
$id = input("id/d");
$info = AccountLevel::findById($id);
if ($this->request->isPost()) {
if (empty($info)) {
return $this->json(4001, "信息不存在");
}
$item = $this->request->only(["name", "rights", "poster", "value", "content"]);
$rule = [
'name|名称' => 'require',
'value|成长值' => 'require',
];
$validate = $this->validateByApi($item, $rule);
if ($validate !== true) {
return $validate;
}
try {
AccountLevel::updateById($id, $item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
if (empty($info)) {
return $this->error("信息不存在");
}
$this->data['item'] = $info;
//权益列表
$rightsArray = AccountLevel::$rightsArray;
$selectd = [];
if (!empty($info['rights'])) {
$selectd = explode(",", $info['rights']);
}
$this->data['rightsJson'] = AccountLevel::xmSelectJson($selectd);
return $this->view();
}
}

View File

@ -0,0 +1,306 @@
<?php
namespace app\controller\manager;
use app\model\Disease;
use app\model\DoctorRelation;
use app\model\Archives as ArchivesModel;
use app\model\ArchivesCategory as ArticleCategoryModel;
use app\model\ArchivesModelField;
use app\model\Spu;
use app\model\Config;
use app\repository\AccountRepository;
use app\repository\CmsRepository;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\response\Json;
use think\response\View;
/**
* 档案管理|内容管理
*
* Class Archives
* @package app\controller\manager
*/
class Archives extends Base
{
protected $noNeedLogin = ['getDiseaseList', 'getDoctorList', 'getDiaryList', 'getCourseList'];
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
ArchivesModel::deleteByIds($ids);
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = ArchivesModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
if (isset($item['video_src'])) {
$item['video'] = $item['video_src'];
unset($item['video_src']);
}
$validate = $this->validateByApi($item, [
'category_id|栏目' => 'require|gt:0',
'title|标题' => 'require|max:255',
'summary|摘要' => 'max:255',
'content|内容' => 'require',
], ['category_id.gt' => '请选择栏目']);
if ($validate !== true) {
return $validate;
}
try {
$now = date('Y-m-d H:i:s');
$item['updated_at'] = $now;
$item['updated_by'] = $this->auth['user_id'];
$info->save($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表
$currentShowFields = $showFieldList[$info['category_id']] ?? [];//当前选中栏目 可展示字段列表
$this->data['item'] = $info;
$this->data['jsonList'] = $this->xmSelectJson([$info['category_id']]);
$this->data['currentList'] = $currentShowFields;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = ArchivesModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
$categoryId = input('category_id/d', 0);
if ($this->request->isPost()) {
$item = input('post.');
if (isset($item['video_src'])) {
$item['video'] = $item['video_src'];
unset($item['video_src']);
}
$validate = $this->validateByApi($item, [
'category_id|栏目' => 'require|gt:0',
'title|标题' => 'require|max:255',
'summary|摘要' => 'max:255',
'content|内容' => 'require',
], ['category_id.gt' => '请选择栏目']);
if ($validate !== true) {
return $validate;
}
try {
$now = date('Y-m-d H:i:s');
$item['created_at'] = $now;
$item['published_at'] = $now;
$item['created_by'] = $this->auth['user_id'];
ArchivesModel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表
//有指定栏目获取指定栏目 否则获取第一个 可展示字段列表
$currentShowFields = $categoryId > 0 ? ($showFieldList[$categoryId] ?? []) : array_values($showFieldList)[0];
$this->data['categoryId'] = $categoryId ?? 0;
$this->data['diaryId'] = $diaryId ?? 0;
$this->data['jsonList'] = $this->xmSelectJson([$categoryId]);
$this->data['showList'] = json_encode($showFieldList, JSON_UNESCAPED_UNICODE);
$this->data['currentList'] = $currentShowFields;
return $this->view();
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
$categoryId = input('category_id/d', 0);
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$searchParams = input('searchParams');
$where = [];
if ($categoryId > 0) {
$where[] = ['category_id', '=', $categoryId];
}
if ($searchParams) {
foreach ($searchParams as $key => $param) {
if (!empty($param)) {
if (is_string($param)) {
$where[] = [$key, 'like', '%'.$param.'%'];
} elseif (is_array($param)) {
//数组空元素去除
foreach ($param as $k => $val) {
if (empty($val)) {
unset($param[$k]);
}
}
if (!empty($param)) {
$where[] = [$key, 'in', $param];
}
}
}
}
}
$items = ArchivesModel::findList($where, [], $page, $limit, function ($q) {
return $q->with(['member', 'category'])
->order('sort', 'desc')
->order('id', 'desc');
});
return $this->json(0, '操作成功', $items);
}
$selected = $categoryId > 0 ? [$categoryId] : [];
$this->data['categoryId'] = $categoryId;
$this->data['categoryJson'] = $this->categoryJson($selected);
$this->data['archivesPath'] = '/'.Config::MINI_PATH_ARCHIVES;
return $this->view();
}
/**
* 构造分类 json数据[zTree用]
*
* @param array $selected
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function categoryJson(array $selected = [])
{
$category = ArticleCategoryModel::order('sort', 'desc')
->field('id,pid,title')
->select()
->toArray();
foreach ($category as $k => $m) {
$category[$k]['checked'] = in_array($m['id'], $selected);
$category[$k]['spread'] = true;
}
$category = CmsRepository::getInstance()->buildMenuChild(0, $category);
return json_encode($category, JSON_UNESCAPED_UNICODE);
}
/**
* 内容分类 构造xmSelect json数据[xmSelect用]
*
* @param array $selected
* @param array $disabled
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function xmSelectJson(array $selected = [], array $disabled = [])
{
$category = ArticleCategoryModel::order('sort', 'desc')
->field('id,pid,title')
->select()
->toArray();
foreach ($category as $k => $m) {
$category[$k]['selected'] = in_array($m['id'], $selected);
$category[$k]['disabled'] = in_array($m['id'], $disabled);
}
$category = CmsRepository::getInstance()->buildMenuChild(0, $category);
$category = CmsRepository::getInstance()->handleSelectedList($category);
return json_encode($category, JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,256 @@
<?php
namespace app\controller\manager;
use app\repository\CmsRepository;
use app\model\Log;
use app\model\ArchivesCategory as ArchivesCategoryModel;
use app\validate\MenuValidate;
use Exception;
use think\facade\Cache;
use think\facade\Db;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\response\Json;
use think\response\View;
/**
* 栏目管理
*
* Class Menu
* @package app\controller\manager
*/
class ArchivesCategory extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
Cache::delete("categoryNames");//删除缓存
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
if (ArchivesCategoryModel::hasChildrenByIds($ids)) {
return $this->json(4002, '待删除数据存在子数据');
}
if (ArchivesCategoryModel::hasContentByIds($ids)) {
return $this->json(4002, '待删除数据存在内容文章');
}
ArchivesCategoryModel::deleteByIds($ids);
// Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
Cache::delete("categoryNames");//删除缓存
$id = input('id/d', 0);
if (!$info = ArchivesCategoryModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'pid|父级分类' => 'require|number',
'model_id|所属模型' => 'require|number|gt:0',
'title|标题' => 'require|max:100',
'name|标识' => 'unique:archives_category,name,'.$info['id'] ?? 0,
'description|描述' => 'max:255',
], ['model_id' => '所属模型必需选择']);
if ($validate !== true) {
return $validate;
}
Db::startTrans();
try {
$oldPath = $info['path'] ?? '';
$item['path'] = ArchivesCategoryModel::getPath($item['pid']);
$info->save($item);
//刷新所有路径
$oldPath = $oldPath.','.$id;
$newPath = $item['path'].','.$id;
if ($oldPath != $newPath) {
ArchivesCategoryModel::refreshPath();
}
Db::commit();
return $this->json();
} catch (ValidateException $e) {
Db::rollback();
return $this->json(4001, $e->getError());
}
}
$disabled = ArchivesCategoryModel::getAllChildrenIds($id);
$disabled[] = $id;
$this->data['jsonList'] = $this->categoryJson([$info['pid']], $disabled);
$this->data['modelList'] = $this->modelJson([$info['model_id']], []);
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function modify(): Json
{
Cache::delete("categoryNames");//删除缓存
if ($this->request->isPost()) {
$item = input('post.');
$validate = new MenuValidate();
if (!$validate->scene('menu_modify')->check($item)) {
return $this->json(4002, $validate->getError());
}
if (!$info = ArchivesCategoryModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
Cache::delete("categoryNames");//删除缓存
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'pid|父级分类' => 'require|number',
'model_id|所属模型' => 'require|number|gt:0',
'title|标题' => 'require|max:100',
'name|标识' => 'require|unique:archives_category',
'description|描述' => 'max:255',
], ['model_id' => '所属模型必需选择']);
if ($validate !== true) {
return $validate;
}
try {
$item['path'] = ArchivesCategoryModel::getPath($item['pid']);
ArchivesCategoryModel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['jsonList'] = $this->categoryJson();
$this->data['modelList'] = $this->modelJson();
return $this->view();
}
/**
* 列表
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function index()
{
if ($this->request->isPost()) {
$menus = ArchivesCategoryModel::getList();
$res = [
'code' => 0,
'msg' => 'success',
'count' => $menus->count(),
'data' => $menus->toArray(),
];
return json($res);
}
return $this->view();
}
/**
* @param array $selected
* @param array $disabled
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function categoryJson(array $selected = [], array $disabled = [])
{
$categoryList[] = ['title' => '顶级分类', 'id' => 0, 'disabled' => false, 'selected' => in_array(0, $selected)];
$menus = ArchivesCategoryModel::getList();
$menus = $menus->toArray();
foreach ($menus as $k => $m) {
$menus[$k]['selected'] = in_array($m['id'], $selected);
$menus[$k]['disabled'] = in_array($m['id'], $disabled);
}
$menus = CmsRepository::getInstance()->buildMenuChild(0, $menus);
$categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus));
return json_encode($categoryList, JSON_UNESCAPED_UNICODE);
}
/**
* @param array $selected
* @param array $disabled
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function modelJson(array $selected = [], array $disabled = [])
{
$categoryList[] = ['title' => '全部', 'id' => 0, 'disabled' => false, 'selected' => in_array(0, $selected)];
$menus = \app\model\ArchivesModel::field('id,0 as pid,title,name,sort,true as open')
->order('sort', 'desc')
->order('id', 'asc')
->select();;
$menus = $menus->toArray();
foreach ($menus as $k => $m) {
$menus[$k]['selected'] = in_array($m['id'], $selected);
$menus[$k]['disabled'] = in_array($m['id'], $disabled);
}
$menus = CmsRepository::getInstance()->buildMenuChild(0, $menus);
$categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus));
return json_encode($categoryList, JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use Exception;
use app\model\ArchivesModel as MArchivesModel;
use app\model\Archives;
use app\model\ArchivesModelField;
use think\exception\ValidateException;
use think\facade\Db;
use think\response\Json;
use think\response\View;
/**
* 文档模型管理
* Class ArchivesModel
* @package app\controller\manager
*/
class ArchivesModel extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
$groupNames = MArchivesModel::whereIn('id', $ids)->column('name');
if (ArchivesModelField::whereIn('name', $groupNames)->count() > 0) {
return $this->json(4002, '模型下已存在字段,无法删除!');
}
MArchivesModel::deleteByIds($ids);
// Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 编辑
*
* @return Json|View
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = MArchivesModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title|模型标题' => 'require',
'name|模型标识' => 'alphaDash|unique:archives_model,name,'.$id,
]);
if ($validate !== true) {
return $validate;
}
try {
ArchivesModelField::setFieldList($id, $info['name']);
$info->save($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = MArchivesModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title|模型标题' => 'require',
'name|模型标识' => 'require|alphaDash|unique:archives_model',
]);
if ($validate !== true) {
return $validate;
}
try {
$model = MArchivesModel::create($item);
ArchivesModelField::setFieldList($model['id'], $model['name']);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->view();
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$items = MArchivesModel::findList([], [], $page, $limit, function ($q) {
return $q->order('sort', 'desc')->order('id', 'asc');
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use Exception;
use app\model\ArchivesModelField as MArchivesModelField;
use app\model\ArchivesModel;
use think\exception\ValidateException;
use think\response\Json;
use think\response\View;
/**
* 文档模型字段管理
* Class ArchivesModelField
* @package app\controller\manager
*/
class ArchivesModelField extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
MArchivesModelField::deleteByIds($ids);
// Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 编辑
*
* @return Json|View
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = MArchivesModelField::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title|模型标题' => 'require',
'name|模型标识' => 'alphaDash',
]);
if ($validate !== true) {
return $validate;
}
try {
$info->save($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = MArchivesModelField::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
$modelId = input('model_id/d', 0);
if ($this->request->isPost()) {
$item = input('post.');
$item['model_id'] = $modelId;
$validate = $this->validateByApi($item, [
'title|字段标题' => 'require',
'model_id|模型' => 'require|number|gt:0',
'name|字段标识' => 'require|alphaDash|unique:archives_model_field',
], ['model_id.gt' => '模型不存在']);
if ($validate !== true) {
return $validate;
}
try {
MArchivesModelField::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['modelId'] = $modelId;
return $this->view();
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
$modelId = input('model_id/d', 0);
if (!$modelId) {
return $this->json(4001, '请选择正确的模型');
}
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$where[] = ['model_id', '=', $modelId];
$items = MArchivesModelField::findList($where, [], $page, $limit, function ($q) {
return $q->order('status', 'desc')->order('id', 'asc');
});
return $this->json(0, '操作成功', $items);
}
$this->data['modelId'] = $modelId;
return $this->view();
}
/**
* 同步字段
*
* @return Json
* @throws Exception
*/
public function sync(): Json
{
if ($this->request->isPost()) {
$modelId = input('model_id/d', 0);
if (!$modelId) {
return $this->json(4001, '模型错误');
}
if (!$info = ArchivesModel::findOne(['id' => $modelId])) {
return $this->json(4001, '模型不存在');
}
try {
MArchivesModelField::syncFieldList($modelId, $info['name']);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
}

View File

@ -0,0 +1,488 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use app\service\AliOss;
use think\facade\Config;
use Exception;
use app\model\Attachment as AttachmentModel;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\response\Json;
use think\response\View;
/**
* 附件管理 - 素材管理
* Class Attachment
* @package app\controller\manager
*/
class Attachment extends Base
{
protected $noNeedLogin = ['file', 'getSize', 'md5List', 'pathDirHandle', 'toOss', 'delLostFile', 'test'];
protected $DIRECTORY_SEPARATOR = "/";
protected function initialize()
{
parent::initialize(); // TODO: Change the autogenerated stub
$this->DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR == "\\" ? "/" : DIRECTORY_SEPARATOR;
}
/**
* 删除
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
$items = AttachmentModel::whereIn('id', $ids)->where('is_dir', AttachmentModel::COMMON_ON)->select();
if ($items->where('is_dir', AttachmentModel::COMMON_ON)->count()) {
$dirPaths = [];
foreach ($items->toArray() as $item) {
$dirPaths[] = $item['path'].$item['name']. $this->DIRECTORY_SEPARATOR ;
}
if (AttachmentModel::where('path', 'in', $dirPaths)->count()) {
return $this->json(4001, '待删除目录下存在内容!');
}
}
AttachmentModel::deleteByIds($ids);
// Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 单个字段编辑
*
* @return Json
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = AttachmentModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
if ($item['field'] == 'name' && $info['is_dir'] == AttachmentModel::COMMON_ON) {
return $this->json(4002, '目录名称不能修改');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title|合集标题' => 'require',
'user|虚拟用户' => 'require',
'headimg|虚拟用户头像' => 'require',
]);
if ($validate !== true) {
return $validate;
}
try {
$now = date('Y-m-d H:i:s');
$item['created_at'] = $now;
$item['created_by'] = $this->auth['user_id'];
AttachmentModel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->view();
}
/**
* 添加文件夹
*
* @return Json|View
* @throws Exception
*/
public function addFolder()
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'name|文件夹名称' => 'require|alphaDash|max:20',
'path|文件路径' => 'require',
]);
// 例 name=dir4 path=/storage/dir1/dir2/dir3
// 去首尾/
$path = trim($item['path'], $this->DIRECTORY_SEPARATOR );
// 全路径 如 /storage/dir1/dir2/dir3/dir4/ 注意前后都有/
$fullPath = $this->DIRECTORY_SEPARATOR .$path. $this->DIRECTORY_SEPARATOR .$item['name']. $this->DIRECTORY_SEPARATOR ;
if ($validate !== true) {
return $validate;
}
AttachmentModel::pathDirHandle($fullPath);
return $this->json();
}
$path = input('path/s', AttachmentModel::ROOT_PATH);
$this->data['path'] = $path;
return $this->view();
}
/**
* 图片列表
*
* @return View|Json
* @throws Exception
*/
public function image()
{
$path = input('post.path', AttachmentModel::ROOT_PATH);
$path = $this->DIRECTORY_SEPARATOR . trim($path, $this->DIRECTORY_SEPARATOR). $this->DIRECTORY_SEPARATOR;
$path = str_replace("\\","/",$path);
$type = input('type/s', 'image');
$selected = input('selected', false);
$multiple = input('multiple', false);
Config::load('extra/alioss', 'alioss');
$config = config('alioss');
$oss = $config['customDomain'];
if ($this->request->isPost()) {
$items = $this->list($path, ['image']);
return $this->json(0, '操作成功', $items);
}
$this->data['path'] = $path;
$this->data['oss'] = $oss;
$this->data['type'] = $type;
$this->data['multiple'] = $multiple;
$this->data['selected'] = $selected;
return $this->view();
}
/**
* 视频列表
*
* @return View|Json
* @throws Exception
*/
public function video()
{
$path = input('post.path', AttachmentModel::ROOT_PATH);
$path = $this->DIRECTORY_SEPARATOR . trim($path, $this->DIRECTORY_SEPARATOR ) . $this->DIRECTORY_SEPARATOR ;
$path = str_replace("\\","/",$path);
$type = input('type/s', 'video');
$selected = input('selected', false);
$multiple = input('multiple', false);
Config::load('extra/alioss', 'alioss');
$config = config('alioss');
$oss = $config['customDomain'];
if ($this->request->isPost()) {
$items = $this->list($path, ['video']);
return $this->json(0, '操作成功', $items);
}
$this->data['path'] = $path;
$this->data['oss'] = $oss;
$this->data['type'] = $type;
$this->data['multiple'] = $multiple;
$this->data['selected'] = $selected;
return $this->view();
}
/**
* 文件列表
*
* @return View|Json
* @throws Exception
*/
public function file()
{
Config::load('extra/alioss', 'alioss');
$config = config('alioss');
$oss = $config['customDomain'];
$type = input('type/s', 'all');
$selected = input('selected', false);
$multiple = input('multiple', false);
if ($this->request->isPost()) {
$page = input('post.page', 1);
$size = input('post.size', 20);
$searchParams = input('searchParams');
$where = [];
if ($searchParams) {
foreach ($searchParams as $key => $param) {
if (!empty($param)) {
if (is_string($param)) {
if ($key == 'is_oss') {
if ($param >= 0) {
$where[] = ['is_oss', '=', $param];
}
} else {
$where[] = [$key, 'like', '%'.$param.'%'];
}
} elseif (is_array($param)) {
//数组空元素去除
foreach ($param as $k => $val) {
if (empty($val)) {
unset($param[$k]);
}
}
if (!empty($param)) {
$where[] = [$key, 'in', $param];
}
}
}
}
}
if ($type !== 'all') {
$where[] = ['type', '=', $type];
}
$items = AttachmentModel::findList($where, [], $page, $size, function ($q) {
return $q->where('type', '<>', 'dir')->order('updated_at', 'desc');
});
$items['list']->each(function ($item) {
$item->size_text = getFilesize($item['size'] ?? 0);
});
return $this->json(0, '操作成功', $items);
}
$this->data['oss'] = $oss;
$this->data['type'] = $type;
$this->data['multiple'] = $multiple;
$this->data['selected'] = $selected;
return $this->view();
}
/**
* 一键删除失效记录 即oss不存在&&本地不存在
*
* @return Json
*/
public function delLostFile(): Json
{
if ($this->request->isPost()) {
$total = AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR)
->where('is_dir', AttachmentModel::COMMON_OFF)
->where('is_oss', AttachmentModel::COMMON_OFF)
->where('has_local', AttachmentModel::COMMON_OFF)
->count();
if ($total === 0) {
return $this->json(0, 'success', ['total' => $total]);
}
if (AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR)
->where('is_dir', AttachmentModel::COMMON_OFF)
->where('is_oss', AttachmentModel::COMMON_OFF)
->where('has_local', AttachmentModel::COMMON_OFF)
->delete()) {
return $this->json(0, 'success', ['total' => $total]);
}
return $this->json(4004, '删除失败');
}
return $this->json(4000, '请求错误');
}
/**
* 一键上传本地文件到OSS
*
* @return Json
*/
public function toOss(): Json
{
if ($this->request->isPost()) {
Config::load('extra/alioss', 'alioss');
$config = config('alioss');
$ossObject = AliOss::instance();
$bucket = $config['bucket'];
$total = AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR)
->where('is_dir', AttachmentModel::COMMON_OFF)
->where('is_oss', AttachmentModel::COMMON_OFF)
->field('id')
->count();
$done = 0;
$none = 0;
if ($total === 0) {
return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]);
}
try {
AttachmentModel::where('type', '<>', AttachmentModel::TYPE_DIR)
->where('is_dir', AttachmentModel::COMMON_OFF)
->where('is_oss', AttachmentModel::COMMON_OFF)
->field('id,src')
->chunk(3, function ($items) use ($ossObject, $bucket, &$done, &$none) {
$doneIds = [];
$noneIds = [];
foreach ($items as $item) {
if ($item['src']) {
$realPath = public_path().ltrim($item['src'], $this->DIRECTORY_SEPARATOR );
if (!file_exists($realPath)) {
$none++;
$noneIds[] = $item['id'];
continue;
}
$pathInfo = pathinfo($item['src']);
$object = ltrim($item['src'], $this->DIRECTORY_SEPARATOR );
//是否存在
if (!$ossObject->doesObjectExist($bucket, $object)) {
//创建目录
$ossObject->createObjectDir($bucket, ltrim($pathInfo['dirname'], $this->DIRECTORY_SEPARATOR ));
$ossObject->uploadFile($bucket, $object, $realPath);
}
$doneIds[] = $item['id'];
$done++;
}
}
// 失效标记
if ($noneIds) {
$update = ['is_oss' => AttachmentModel::COMMON_OFF, 'has_local' => AttachmentModel::COMMON_OFF];
(new AttachmentModel())->where('id', 'in', $noneIds)->update($update);
}
// 完成标记
if ($doneIds) {
$update = ['is_oss' => AttachmentModel::COMMON_ON];
(new AttachmentModel())->where('id', 'in', $doneIds)->update($update);
}
});
return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]);
} catch (Exception $e) {
\think\facade\Log::error('本地文件一键上传OSS失败 '.$e->getMessage());
return $this->json(0, 'success', ['total' => $total, 'done' => $done, 'none' => $none]);
}
}
}
/**
* 指定类型附件列表
*
* @param array $type
* @param string $path
* @return array
* @throws Exception
*/
protected function list(string $path, array $type): array
{
$type[] = 'dir';
$where[] = ['path', '=', $path];
$where[] = ['type', 'in', $type];
$items = AttachmentModel::findList($where, [], 1, 0, function ($q) {
return $q->order('is_dir', 'desc')->order('updated_at', 'desc');
});
$items['list']->each(function ($item) {
$item->size_text = getFilesize($item['size'] ?? 0);
});
$items['path'] = $path;
return $items;
}
/**
* 获取文件大小
*
* @return Json
*/
public function getSize(): Json
{
$path = input('post.path', '');
$types = input('post.type/a', []);
$size = '';
if (empty($path)) {
return $this->json(0, '操作成功', $size);
}
$path = str_replace("\\","/",$path);
$total = AttachmentModel::where('path', 'like', $path.'%')
->when(!empty($types), function ($q) use ($types) {
$q->where('type', 'in', $types);
})
->sum('size');
return $this->json(0, '操作成功', getFilesize($total));
}
// 将没有md5的文件 更新md5 仅针对本地文件
public function md5List()
{
$noMd5List = AttachmentModel::whereNull('md5')->select();
$update = [];
foreach ($noMd5List as $item) {
try {
if (!empty($item['src'])) {
$arr = [];
$path = public_path().ltrim($item['src'], $this->DIRECTORY_SEPARATOR );
$file = new \think\File($path);
$arr['md5'] = $file->md5();
$arr['id'] = $item['id'];
$update[] = $arr;
}
} catch (Exception $e) {
continue;
}
}
(new AttachmentModel())->saveAll($update);
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace app\controller\manager;
use think\facade\{Db, Config, Env};
use app\model\{Log as MLog};
use app\service\Tool;
class Backup extends Base
{
/**
* 因受mysql 配置参数net_buffer_length和max_allowed_packet大小的限制因此insert语句改为单条插入
* show VARIABLES like "%net_buffer_length%" 默认单条sql语句长度16k
* show VARIABLES like "%max_allowed_packet%" 默认单个sql文件最大容量
* sql 文件注释 -- + 空格之后填写注释内容
* 因根目录文件夹权限限制因此需要手动创建backup/data目录并赋予权限www用户组
*/
public function back()
{
ini_set('max_execution_time', 0);
ini_set("memory_limit",-1);
set_time_limit(0);
$path = Config::get('filesystem.disks.backup.root') . '/';
$dataBase = Env::get('database.database');
// 不要使用单引号,双引号中的变量可以解析,单引号就是绝对的字符串
$eol = "\r\n";
$eolB = "\r\n\r\n";
$info = '-- ------------------------------'.$eol;
$info .= '-- 日期: '.date('Y-m-d H:i:s',time()).$eol;
$info .= '-- MySQL --Database - '.$dataBase.$eol;
$info .= '-- ------------------------------'.$eol;
$info .= 'CREATE DATABASE IF NOT EXISTS `'.$dataBase.'` DEFAULT CHARACTER SET "utf8mb4" COLLATE "utf8mb4_general_ci";'.$eolB;
$info .= 'USE `'.$dataBase.'`;'.$eol;
if(is_dir($path)) {
if(!is_writable($path)) {
chmod($path, 0755);
}
} else {
mkdir($path, 0755, true);
}
$fileName = $path.$dataBase.'_'.date('YmdHis',time()).'.sql';
if(file_exists($fileName)) {
@unlink($fileName);
}
// 以追加的方式写入
file_put_contents($fileName, $info, FILE_APPEND);
$tables = Db::query('show tables');
foreach ($tables as $table) {
// 表结构
$tableName = $table['Tables_in_'.$dataBase];
$sqlTable = 'SHOW CREATE TABLE '.$tableName;
$res = Db::query($sqlTable);
$infoTable = '-- ------------------------------'.$eol;
$infoTable .= '-- Table structure for `'.$tableName.'`'.$eol;
$infoTable .= '-- ------------------------------'.$eolB;
$infoTable .= 'DROP TABLE IF EXISTS `'.$tableName.'`;'.$eolB;
$infoTable .= $res[0]['Create Table'].';'.$eolB;
// 表数据
$infoTable .= '-- ------------------------------'.$eol;
$infoTable .= '-- Data for the table `'.$tableName.'`'.$eol;
$infoTable .= '-- ------------------------------'.$eolB;
file_put_contents($fileName, $infoTable, FILE_APPEND);
$sqlData = 'select * from '.$tableName;
$data = Db::query($sqlData);
$count = count($data);
if ($count > 0) {
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
foreach ($data as $k => $item) {
$valStr = '(';
foreach ($item as $val) {
// 字符串转义
$val = addslashes($val);
$valStr .= '"'.$val.'", ';
}
// 去除最后的逗号和空格
$valStr = substr($valStr,0,strlen($valStr)-2);
// 限制单条sql语句在16K以内可根据mysql配置条件进行调整
$sqlLength = strlen($dataStr) + strlen($valStr);
if($sqlLength >= (1024 * 16 - 10)) {
$dataStr .= $valStr.');'.$eol;
file_put_contents($fileName, $dataStr, FILE_APPEND);
// 提前分段写入后需重置数据语句
if ($k <= ($count-1)) {
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
} else {
$dataStr = '';
}
} else {
if ($k < ($count-1)) {
$dataStr .= $valStr.'),'.$eol;
} else {
$dataStr .= $valStr.');'.$eolB;
}
}
}
file_put_contents($fileName, $dataStr, FILE_APPEND);
}
}
clearstatcache();
$backups = explode('/', $fileName);
$backupName = $backups[count($backups)-1];
$src = Config::get('filesystem.disks.backup.url') . '/';
return $this->json(0, '备份成功:' . $src . $backupName);
}
public function index()
{
$path = Config::get('filesystem.disks.backup.root') . '/';
$src = Config::get('filesystem.disks.backup.url') . '/';
$items = [];
if(is_dir($path)) {
if(!is_readable($path)) {
chmod($path,0755);
}
$files = scandir($path);
foreach ($files as $file) {
if($file != '.' && $file != '..') {
$ext = substr($file, -4);
if ($ext == '.sql') {
$creatTime = substr($file, -18, 14);
$items[] = [
'file' => $file,
'path' => $src . $file,
'time' =>is_numeric($creatTime) ? date('Y-m-d H:i:s', strtotime($creatTime)) : '',
];
}
}
}
}
clearstatcache();
$this->data['items'] = $items;
$this->data['backupPath'] = str_replace('\\','/', $path);
return $this->view();
}
public function del()
{
if (request()->isPost()) {
$filePath = input('post.id');
Tool::delFile($filePath);
MLog::write('backup', 'del', '删除了备份数据文件,文件路径为:' . $filePath);
return $this->json();
} else {
return $this->json(1, '非法请求');
}
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace app\controller\manager;
use app\controller\BaseController;
use app\service\File as FileTool;
use Exception;
use think\exception\ValidateException;
use think\response\Json;
use think\response\Redirect;
use think\response\View;
/**
* 控制器基础类
*/
class Base extends BaseController
{
protected $data = [];
protected $auth = null;
protected function initialize()
{
$this->middleware = [
'auth' => ['except' => array_merge($this->noNeedLogin, $this->noNeedRight)],
'log'
// 'jwt' => ['except' => $this->noNeedRight],
];
$this->auth = session('auth');
$this->data['member'] = $this->auth;
$this->data['_token'] = $this->auth['token'] ?? '';
$this->data['groupId'] = $this->auth['groupId'] ?? 0;
$this->fileDomain();
}
//变量赋值到模板
protected function view(string $template = '')
{
return view($template)->assign($this->data);
}
/**
* @param string $msg
* @param string|null $url
* @param string $data
* @param int $wait
* @return Redirect
*/
protected function error($msg = '', string $url = null, $data = '', int $wait = 3): Redirect
{
if (is_null($url)) {
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
} elseif ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
}
$result = [
'code' => 0,
'msg' => $msg,
'data' => $data,
'url' => $url,
'wait' => $wait,
];
return $this->redirect(url('/manager/error/jump', $result));
}
public function __call($name, $args)
{
return $this->view('/manager/error/jump');
}
/**
* 验证器
*
* @param array $data
* @param $validate
* @param array $message
* @param bool $batch
* @return Redirect
* @throws Exception
*/
protected function validateError(array $data, $validate, array $message = [], bool $batch = false): Redirect
{
try {
parent::validate($data, $validate, $message, $batch);
} catch (ValidateException $e) {
$msg = $e->getMessage();
if ($batch) {
$msg = implode(',', $e->getError());
}
return $this->error($msg);
} catch (Exception $e) {
throw $e;
}
}
/**
* 文件域名前缀
*/
public function fileDomain()
{
$this->data['fileDomain'] = FileTool::getFileDomain();
}
}

View File

@ -0,0 +1,224 @@
<?php
// +----------------------------------------------------------------------
// | HisiPHP框架[基于ThinkPHP5.1开发]
// +----------------------------------------------------------------------
// | Copyright (c) 2016-2021 http://www.hisiphp.com
// +----------------------------------------------------------------------
// | HisiPHP承诺基础框架永久免费开源您可用于学习和商用但必须保留软件版权信息。
// +----------------------------------------------------------------------
// | Author: 橘子俊 <364666827@qq.com>开发者QQ群50304283
// +----------------------------------------------------------------------
namespace app\controller\manager;
use app\model\ArchivesCategory as ArticleCategoryModel;
use app\model\ArchivesModelField;
use app\model\Block as BlockModel;
use app\model\System;
use app\validate\Block as VBlock;
use app\repository\CmsRepository;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
/**
* 碎片控制器
* @package app\controller\cms
*/
class Block extends Base
{
protected function initialize()
{
parent::initialize();
$action = $this->request->action();
$cid = $this->request->param('cid/d');
if (($action == 'add' || $action == 'edit') && !$this->request->isPost()) {
$showFieldList = ArchivesModelField::showFieldList();//所有栏目 可展示字段列表
$currentShowFields = $showFieldList[$cid] ?? [];//当前选中栏目 可展示字段列表
$this->data['system'] = System::getSystem();
$this->data['currentList'] = $currentShowFields;
}
$this->data['jsonList'] = $this->xmSelectJson([$cid]);
}
public function index()
{
if ($this->request->isAjax()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$keyword = input('searchParams.keyword/s');
$categoryId = input('searchParams.category_id/d', 0);
$where = [];
if ($categoryId > 0) {
$where[] = ['category_id', '=', $categoryId];
}
if (!empty($keyword)) {
$where[] = ["name|title", 'like', '%'.$keyword.'%'];
}
$items = BlockModel::findList($where, [], $page, $limit, function ($q) {
return $q->with(["category"])->withAttr("content",function ($value,$data){
switch ($data["type"]){
case BlockModel::BLOCK://
return $value;
break;
case BlockModel::TEXT:
return $value;
break;
case BlockModel::IMG:
return "<img src='{$value}'/>";
break;
case BlockModel::GROUP:
$data = explode(",",$value);
$str = "";
foreach ($data as $vdata){
$str.="<img src='{$vdata}'/>";
}
return $str;
case BlockModel::FILE:
return $value;
break;
case BlockModel::VIDEO:
return $value;
break;
case BlockModel::ING_LIST:
return $value;
break;
}
});
},["id"=>"desc"]);
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
public function add(){
if ($this->request->isPost()) {
$postData = $this->request->post();
try {
$this->validate($postData, VBlock::class);
$postData["content"] = $postData["content".$postData["type"]];
$hasWhere =[["name","=",$postData["name"]]] ;
if( $postData["category_id"] > 0 ){
$hasWhere[] = ["category_id","=",$postData["category_id"]];
}
$other = BlockModel::findOne($hasWhere);
if(!empty($other)){
throw new ValidateException("键值重复");
}
}catch (ValidateException $e){
return $this->json(4001,$e->getError());
}
try {
BlockModel::create($postData);
return $this->json();
}catch (\Exception $e){
return $this->json(0,'保存失败'. $e->getMessage());
}
}
$this->data["types"] = BlockModel::getTypes();
return $this->view();
}
public function edit(){
$id = input("id/d");
$item = BlockModel::findById($id);
if ($this->request->isPost()) {
$postData = $this->request->post();
try {
$this->validate($postData, VBlock::class);
$postData["content"] = $postData["content".$postData["type"]];
$hasWhere =[["name","=",$postData["name"]],["id","<>",$id]] ;
if( $postData["category_id"] > 0 ){
$hasWhere[] = ["category_id","=",$postData["category_id"]];
}else{
$hasWhere[] = ["category_id","=",0];
}
$other = BlockModel::findOne($hasWhere);
if(!empty($other)){
throw new ValidateException("键值重复");
}
}catch (ValidateException $e){
return $this->json(4001,$e->getError());
}
try {
$item->save($postData);
return $this->json();
}catch (\Exception $e){
return $this->json(0,'保存失败'. $e->getMessage());
}
}
if(empty($item)){
return $this->error("碎片不存在");
}
$item =$item->toArray();
if($item['type'] == BlockModel::ING_LIST){
$contentKey = array_keys($item['content']);
$this->data['maxKey'] = max($contentKey)+1;
}else{
$this->data['maxKey'] = 0;
}
$this->data["item"] = $item;
$this->data["types"] = BlockModel::getTypes();
return $this->view();
}
/**
* 删除
*
* @return Json
*/
public function del()
{
if ($this->request->isPost()) {
$id = input('id/d', 0);
BlockModel::deleteById($id);
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 内容分类 构造xmSelect json数据[xmSelect用]
*
* @param array $selected
* @param array $disabled
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function xmSelectJson(array $selected = [], array $disabled = [])
{
$category = ArticleCategoryModel::order('sort', 'desc')
->field('id,pid,title')
->select()
->toArray();
foreach ($category as $k => $m) {
$category[$k]['selected'] = in_array($m['id'], $selected);
$category[$k]['disabled'] = in_array($m['id'], $disabled);
}
$category = CmsRepository::getInstance()->buildMenuChild(0, $category);
$category = CmsRepository::getInstance()->handleSelectedList($category);
return json_encode($category, JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace app\controller\manager;
use Exception;
use think\facade\Config as CConfig;
/**
* 额外配置
* Class Config
* @package app\controller\manager
*/
class Config extends Base
{
private string $extraPath = '';
protected function initialize()
{
parent::initialize();
$this->extraPath = config_path() . 'extra/';
if (!is_dir($this->extraPath)) {
if (is_writable(config_path())) {
mkdir($this->extraPath, 0777, true);
} else {
halt('请联系系统管理人员配置文件夹读写权限!请添加'.$this->extraPath.'文件夹的读写权限');
}
} elseif (!is_writable($this->extraPath)) {
halt('请联系系统管理人员配置文件夹读写权限!请添加'.$this->extraPath.'文件夹的读写权限');
}
}
public function wechat()
{
if ($this->request->isPost()) {
$data = input("post.");
unset($data['_token']);
$php = var_export($data, true);
file_put_contents($this->extraPath . 'wechat.php', '<?php' . PHP_EOL . 'return ' . $php . ';');
return $this->json();
} else {
CConfig::load('extra/wechat', 'wechat');
$this->data['item'] = config('wechat');
return $this->view();
}
}
public function alipay()
{
if ($this->request->isPost()) {
$data = input("post.");
unset($data['_token']);
$php = var_export($data, true);
file_put_contents($this->extraPath . 'alipay.php', '<?php' . PHP_EOL . 'return ' . $php . ';');
return $this->json();
} else {
CConfig::load('extra/alipay', 'alipay');
$this->data['item'] = config('alipay');
return $this->view();
}
}
public function __call($name, $args)
{
if ($this->request->isPost()) {
try {
$data = input("post.");
$php = var_export($data, true);
file_put_contents(config_path().'extra/'.$name.'.php', '<?php'.PHP_EOL.'return '.$php.';');
return $this->json();
} catch (Exception $e) {
return $this->json(4001, $e->getMessage());
}
} else {
CConfig::load('extra/'.$name, $name);
$this->data['item'] = config($name);
$this->data['action'] = $name;
return $this->view('manager/config/'.$name);
}
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace app\controller\manager;
class Error
{
public function jump()
{
$param = request()->param();
return view()->assign($param);
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace app\controller\manager;
use app\model\Feedback as FeedbackModel;
use app\model\Log;
use Exception;
use think\response\Json;
use think\response\View;
/**
* 意见反馈
*
* Class Feedback
* @package app\controller\manager
*/
class Feedback extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
FeedbackModel::deleteByIds($ids);
Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$searchParams = input('searchParams', []);
$search = [];
if ($searchParams) {
$searchParams = array_map('trim', $searchParams);
if (isset($searchParams['user_keyword']) && !empty($searchParams['user_keyword'])) {
$search[] = ['user_name|user_tel|user_email', 'like', '%'.$searchParams['user_keyword'].'%'];
}
unset($searchParams['user_keyword']);
foreach ($searchParams as $key => $param) {
if ($param) {
$search[] = [$key, 'like', '%'.$param.'%'];
}
}
}
$items = FeedbackModel::findList($search, [], $page, $limit, function ($q) {
return $q->with(['account'])->order('id', 'desc');
});
$items['list'] = $items['list']->each(function ($item) {
if ($item->account) {
$item->user_name = empty($item->user_name) ? ($item->account->nickname ?? '') : $item->user_name;
$item->user_tel = empty($item->user_tel) ? ($item->account->mobile ?? '') : $item->user_tel;
}
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace app\controller\manager;
use app\model\{File as MFile, Archives, Block, Category, Link, Slide, Log};
use app\service\Tool;
class File extends Base
{
protected $noNeedLogin = ['delPath', 'del', 'unuse', 'getAllFilesInUse', 'list', 'index'];
//删除磁盘上的文件
public function delPath()
{
if ($this->request->isPost()) {
$paths = input('post.paths/a');
if(!empty($paths)){
foreach($paths as $path){
Tool::delFile($path);
}
Log::write('file', 'delPath', '批量删除了磁盘文件,涉及到的文件路径为:' . implode(',', $paths));
return $this->json();
}
return $this->json(2, '待删除文件列表为空');
}
return $this->json(1, '非法请求!');
}
//删除文件记录
public function del()
{
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MFile::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
if($item['type'] == MFile::IMG){
Tool::delFile($item['src'], 1);
}else{
Tool::delFile($item['src']);
}
}
MFile::destroy($delIds);
Log::write('file', 'del', '批量删除了文件涉及到的文件ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除文件列表为空');
}
}
return $this->json(1, '非法请求!');
}
/**
* 未使用文件列表,
* 1. 遍历数据库中使用的图片视频及文件路径
* 2. 遍历上传目录中的文件
* 3. 数据对比,找出存在目录中的文件&不在数据库中的文件
* 4. 页面上显示查找出来的文件
*/
public function unuse()
{
$filesInUse = $this->getAllFilesInUse(); //数据库中在使用的文件
$rootPath = app()->getRootPath();
$uploadPath = $rootPath . 'storage';
$uploadedFiles = getAllFilesByPath($uploadPath, $rootPath); //磁盘上上传的文件
$files = MFile::getAll();
$dbUploadedFiles = []; //数据库中上传的文件
foreach($files as $file){
$src = trim($file['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$dbUploadedFiles[$key] = $file;
}
}
$uploadedNotInUseFiles = array_diff_key($uploadedFiles, $filesInUse); //磁盘上上传未使用的文件
$dbUploadedNotInUseFiles = array_diff_key($dbUploadedFiles, $filesInUse); //数据库中上传未使用的文件
$bothNotInUseFiles = array_intersect_key($uploadedNotInUseFiles, $dbUploadedNotInUseFiles); //磁盘和数据库中,两边都未使用
$this->data['uploadedNotInUseFiles'] = $uploadedNotInUseFiles;
$this->data['dbUploadedNotInUseFiles'] = $dbUploadedNotInUseFiles;
$this->data['bothNotInUseFilesKey'] = array_keys($bothNotInUseFiles);
return $this->view();
}
//获取所有在使用的文件
private function getAllFilesInUse()
{
$files = [];
$blockFiles = Block::getFilesInUse();
if(!empty($blockFiles)){
$files = array_merge($files, $blockFiles);
}
$slideFiles = Slide::getFilesInUse();
if(!empty($slideFiles)){
$files = array_merge($files, $slideFiles);
}
$linkFiles = Link::getFilesInUse();
if(!empty($linkFiles)){
$files = array_merge($files, $linkFiles);
}
$categoryFiles = Category::getFilesInUse();
if(!empty($categoryFiles)){
$files = array_merge($files, $categoryFiles);
}
$articleFiles = Archives::getFilesInUse();
if(!empty($articleFiles)){
$files = array_merge($files, $articleFiles);
}
return $files;
}
//ajax获取文件列表
public function list()
{
if($this->request->isAjax()){
$page = input('param.page/d', 1);
$size = input('param.size/d', 20);
if(!is_integer($page) || $page < 1){
$page = 1;
}
if (!is_integer($size) || $size < 1) {
$size = 20;
}
$type = input('param.type', '');
if(!in_array($type, array_keys(MFile::getTypes()))){
$type = '';
}
$items = MFile::getList($type, $page, $size);
return $this->json(0, 'ok', $items);
}
return $this->json(1, '无此操作');
}
//列表
public function index()
{
$items = MFile::getListPage();
$this->data['items'] = $items;
$this->data['types'] = MFile::getTypes();
return $this->view();
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace app\controller\manager;
use app\model\AccountRecord;
use app\model\Overview;
use app\model\AccountRole;
use app\model\Appointment;
use app\model\Account;
use app\repository\CmsRepository;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
use think\response\View;
use app\model\Member;
use app\model\Menu;
class Index extends Base
{
protected $noNeedLogin = ['index', 'init'];
/**
* 后台初始页面 随后进入dashboard页面
*
* @return View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function index(): View
{
$auth = session('auth');
$this->data['user'] = Member::findById($auth['user_id'] ?? 0, ['id', 'username', 'nickname', 'mobile']);
return $this->view();
}
/**
* 控制台
*
* @return View
* @throws Exception
*/
public function dashboard(): View
{
return $this->view();
}
/**
* 菜单初始化
*
* @return Json
*/
public function init(): Json
{
$res = [];
$res['homeInfo'] = ['title' => '控制台', 'href' => "manager/index/dashboard"];
$res['logoInfo'] = ['title' => 'CMS管理后台', 'href' => "", 'image' => '/static/manager/image/logo1.png'];
$menus = CmsRepository::getInstance()->getMenuList(Menu::TYPE_MENU, Menu::SHOW_YES)->toArray();
$userId = $this->auth['user_id'] ?? 0;
$menus = CmsRepository::getInstance()->handMenuRule($userId, $menus);
$menus = CmsRepository::getInstance()->buildMenuChild(0, $menus, 'child');
$res['menuInfo'] = $menus;
return json($res);
}
/**
* 缓存清理
*
* @return Json
*/
public function clear(): Json
{
$res = ['code' => 1, 'msg' => '服务端清理缓存成功'];
sleep(2);
return json($res);
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace app\controller\manager;
use app\model\{Link as MLink, System, Log};
use app\validate\Link as VLink;
use think\exception\ValidateException;
use think\facade\Cache;
class Link extends Base
{
//删除
public function del()
{
Cache::delete('links');//删除缓存
if ($this->request->isPost()) {
$ids = input('post.ids/a');
if(empty($ids) || !is_array($ids)) {
return $this->json(2, '参数错误,请核对之后再操作!');
}
$items = MLink::getListByIds($ids);
if(!empty($items)){
$delIds = [];
foreach($items as $item){
$delIds[] = $item['id'];
}
MLink::destroy($delIds);
Log::write('link', 'del', '删除了友情链接涉及到的ID为' . implode(',', $delIds));
return $this->json();
}else{
return $this->json(3, '待删除友情链接为空');
}
}
return $this->json(1, '非法请求!');
}
//排序
public function sort()
{
Cache::delete('links');//删除缓存
if($this->request->isPost()){
$id = input('post.id/d');
$sort = input('post.sort/d');
$item = MLink::findById($id);
if(empty($item)){
return $this->json(3, '该友情链接信息不存在!');
}
$item->sort = $sort;
$item->save();
return $this->json();
}
return $this->json(1, '非法请求!');
}
//编辑
public function edit()
{
Cache::delete('links');//删除缓存
if($this->request->isPost()){
$item = input('post.item/a');
$id = input('post.id/d');
$img = input('post.img');
if(is_numeric($id) && $id > 0) {
$link = MLink::findById($id);
if(empty($link)) {
return $this->json(2, '该友情链接信息不存在!');
}
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VLink::class)->check($item);
$link->save( $item);
Log::write('link', 'edit', "友情链接编辑ID{$id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
return $this->json(1, '参数错误,请核对之后再操作!');
} else {
$id = input('param.id/d');
$item = MLink::getById($id);
$imgSize = System::getLinkImageSize();
$this->data['item'] = $item;
$this->data['img_size'] = $imgSize;
return $this->view();
}
}
//添加
public function add()
{
Cache::delete('links');//删除缓存
if($this->request->isPost()){
$item = input('post.item/a');
$img = input('post.img');
if(!empty($img)){
$item['src'] = $img;
}
try {
validate(VLink::class)->check($item);
$link = MLink::create($item);
Log::write('link', 'add', "友情链接新增ID{$link->id} ,标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
} else {
$imgSize = System::getLinkImageSize();
$this->data['img_size'] = $imgSize;
return $this->view();
}
}
public function index()
{
if ($this->request->isPost()) {
$page = $this->request->param('page/d', 1);
$size = $this->request->param('size/d', 30);
$whereMap = [];
$orders = ['sort'=>'asc'];
$list = MLink::findList($whereMap, [], $page, $size, null, $orders);
return $this->json(0, 'success', $list);
}
return $this->view();
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace app\controller\manager;
use app\model\Log as LogModel;
use Exception;
use think\response\Json;
use think\response\View;
/**
* 日志
*
* Class Feedback
* @package app\controller\manager
*/
class Log extends Base
{
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$searchParams = input('searchParams');
$search = [];
if ($searchParams) {
foreach ($searchParams as $key => $param) {
if ($param) {
if ($key == 'begin_time') {
$begin = strtotime($param.' 00:00:00');
$search[] = ['create_time', '>', $begin];
} elseif ($key == 'end_time') {
$end = strtotime($param.' 23:59:59');
$search[] = ['create_time', '<', $end];
} else {
$search[] = [$key, 'like', '%'.$param.'%'];
}
}
}
}
$items = LogModel::findList($search, [], $page, $limit, function ($q) {
return $q->with(['memberName'])->order('create_time', 'desc');
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace app\controller\manager;
use app\service\Jwt;
use Exception;
use app\model\{Member, AuthRule, LoginLog};
use app\controller\BaseController;
use think\response\Json;
use think\response\View;
class Login extends BaseController
{
protected $noNeedLogin = ['index'];
/**
* @return View|Json
* @throws Exception
*/
public function index()
{
if (request()->isPost()) {
$param = input('post.data');
$username = trim($param['username']);
$password = trim($param['password']);
$captcha = trim($param['captcha'] ?? '');
if (!captcha_check($captcha)) {
return $this->json(4001, '验证码错误'.$captcha);
}
if (empty($username) || empty($password)) {
return $this->json(4001, '用户名和密码不能为空');
}
$member = Member::getByUserName($username);
if (empty($member)) {
return $this->json(4002, '用户名或密码错误');
}
if ($member['password'] != md5($password.$username)) {
return $this->json(4003, '用户名或密码错误');
}
if ($member['status'] != Member::STATUS_NORMAL) {
return $this->json(4004, '账号已被禁用');
}
$userInfo = [
'user_id' => $member['id'],
'username' => $member['username'],
'nickname' => $member['nickname'],
'account_id' => $member['account_id'],//绑定的前台用户ID
];
$jwtToken = Jwt::generate($userInfo, env('app.expire', 7200));
$userInfo['token'] = $jwtToken;//jwt生成token
//记录最后登陆时间
$ip = request()->ip();
$time = time();
Member::updateById($member['id'], [
'login_time' => $time,
'login_ip' => $ip
]);
LoginLog::create([
'member_id' => $member['id'],
'name' => $member['username'],
'ip' => $ip,
'create_time' => $time
]);
session('auth', $userInfo);
return $this->json(0, 'success', ['url' => '/manager']);
}
$viewData = [];
return view()->assign($viewData);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace app\controller\manager;
use app\controller\BaseController;
use think\response\Redirect;
class Logout extends BaseController
{
protected $noNeedLogin = ['index'];
/**
* 退出
*
* @return Redirect
*/
public function index(): Redirect
{
session(null);
return redirect(url('/manager/login/index'));
}
}

View File

@ -0,0 +1,387 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use app\model\Member as MemberModel;
use app\model\Role as RoleModel;
use Exception;
use tauthz\facade\Enforcer;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Db;
use think\response\Json;
use think\response\Redirect;
use think\response\View;
/**
* (后台)人员管理
*
* Class Member
* @package app\controller\manager
*/
class Member extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
MemberModel::deleteByIds($ids);
foreach ($ids as $id) {
Enforcer::deleteRolesForUser($id);
}
Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 个人详情
*
* @return Json|View|Redirect
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function profile()
{
$id = $this->auth['user_id'] ?? 0;
if (!$item = MemberModel::findById($id)) {
if ($this->request->isAjax()) {
return $this->json(4001, '记录不存在');
}
return $this->error('记录不存在');
}
if ($this->request->isPost()) {
$post = input('post.');
$validate = $this->validateByApi($post, [
'mobile|手机号' => 'require|unique:member,mobile,'.$id,
'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10',
'remark|备注信息' => 'max:255',
]);
if ($validate !== true) {
return $validate;
}
if (!checkMobile($post['mobile'])) {
return $this->json(4002, '请输入正确的手机号码');
}
try {
$item->save($post);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $item;
return $this->view();
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = MemberModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'mobile|手机号' => 'require|unique:member,mobile,'.$id,
'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10',
'remark|备注信息' => 'max:255',
]);
if ($validate !== true) {
return $validate;
}
if (!checkMobile($item['mobile'])) {
return $this->json(4002, '请输入正确的手机号码');
}
$roles = [];
if ($item['roles']) {
$roles = $item['roles'];
$item['roles'] = implode(',', $item['roles']);
}
Db::startTrans();
try {
$info->save($item);
//删除所有角色
Enforcer::deleteRolesForUser($id);
//新增角色
foreach ($roles as $role) {
Enforcer::addRoleForUser($id, $role);
}
Db::commit();
return $this->json();
} catch (ValidateException $e) {
Db::rollback();
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $info;
$this->data['roleJson'] = $this->roleJson(explode(',', $info['roles']));
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = MemberModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'username|用户名' => 'require|alphaDash|min:4|max:16|unique:member',
'mobile|手机号' => 'require|unique:member',
'nickname|昵称' => 'require|chsAlphaNum|min:2|max:10',
'password|密码' => 'require|min:4|max:16',
'remark|备注信息' => 'max:255',
]);
if ($validate !== true) {
return $validate;
}
if (!checkMobile($item['mobile'])) {
return $this->json(4002, '请输入正确的手机号码');
}
$roles = [];
if ($item['roles']) {
$roles = $item['roles'];
$item['roles'] = implode(',', $item['roles']);
}
Db::startTrans();
try {
$item['password'] = md5($item['password'].$item['username']);
$member = MemberModel::create($item);
foreach ($roles as $role) {
Enforcer::addRoleForUser($member['id'], $role);
}
Db::commit();
return $this->json();
} catch (ValidateException $e) {
Db::rollback();
return $this->json(4001, $e->getError());
}
}
$this->data['roleJson'] = $this->roleJson();
return $this->view();
}
/**
* 修改密码
*
* @return Json|View|Redirect
* @throws Exception
*/
public function password()
{
$id = input('id/d', 0);
if (!$item = MemberModel::findById($id)) {
if ($this->request->isAjax()) {
return $this->json(4001, '记录不存在');
}
return $this->error('记录不存在');
}
if ($this->request->isPost()) {
$post = input('post.');
$validate = $this->validateByApi($post, [
'password|密码' => 'require|confirm',
]);
if ($validate !== true) {
return $validate;
}
$password = md5($post['password'].$item['username']);
try {
$item->save(['password' => $password]);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $item;
return $this->view();
}
/**
* 个人修改密码
*
* @return Json|View
* @throws Exception
*/
public function myPassword()
{
$id = $this->auth['user_id'] ?? 0;
if (!$item = MemberModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$post = input('post.');
$validate = $this->validateByApi($post, [
'old-password|旧密码' => 'require',
'password|密码' => 'require|confirm',
]);
if ($validate !== true) {
return $validate;
}
if ($item['password'] !== md5($post['old-password'].$item['username'])) {
return $this->json(4002, '原始密码错误');
}
$password = md5($post['password'].$item['username']);
try {
$item->save(['password' => $password]);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $item;
return $this->view();
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$searchParams = input('searchParams');
$where = [];
if ($searchParams) {
foreach ($searchParams as $key => $param) {
if (!empty($param)) {
$where[] = [$key, 'like', '%'.$param.'%'];
}
}
}
$items = MemberModel::findList($where, [], $page, $limit, function ($q) {
return $q->order('id', 'desc');
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
/**
* 构造角色json数据
*
* @param array $selected
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function roleJson(array $selected = [])
{
$roles = RoleModel::where('status', RoleModel::STATUS_NORMAL)
->order('sort', 'desc')
->select()
->toArray();
foreach ($roles as $k => $m) {
$roles[$k]['checked'] = in_array($m['id'], $selected);
$roles[$k]['spread'] = true;
}
return json_encode($roles, JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,259 @@
<?php
namespace app\controller\manager;
use app\repository\CmsRepository;
use app\model\Log;
use app\model\Menu as MenuModel;
use app\validate\MenuValidate;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Db;
use think\response\Json;
use think\response\View;
/**
* 菜单管理
*
* Class Menu
* @package app\controller\manager
*/
class Menu extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
$repo = CmsRepository::getInstance();
$repo->delMenuByIds($ids);
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 规则
*
* @return string[]
*/
private function rule(): array
{
return [
'pid|父级菜单' => 'require|number',
'title|标题' => 'require|max:100',
'name|路由标识' => 'require',
'remark|备注信息' => 'max:255',
];
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = MenuModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, $this->rule());
if ($validate !== true) {
return $validate;
}
try {
$oldPath = $info['path'];
$item['path'] = MenuModel::getPath($item['pid']);
$info->save($item);
//刷新所有路径
$oldPath = $oldPath.','.$id;
$newPath = $item['path'].','.$id;
if ($oldPath != $newPath) {
MenuModel::refreshPath();
}
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$disabled = MenuModel::getAllChildrenIds($id);
$disabled[] = $id;
$this->data['menuList'] = $this->menuJson([$info['pid']], $disabled);
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = new MenuValidate();
if (!$validate->scene('menu_modify')->check($item)) {
return $this->json(4002, $validate->getError());
}
if (!$info = MenuModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
$id = input('id/d', 0);
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, $this->rule());
if ($validate !== true) {
return $validate;
}
try {
$item['path'] = MenuModel::getPath($item['pid']);
MenuModel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$selected = $id > 0 ? [$id] : [];
$this->data['menuList'] = $this->menuJson($selected);
return $this->view();
}
/**
* 常规权限生成
*
* @return Json|View
* @throws Exception
*/
public function generate()
{
$id = input('id/d', 0);
if ($this->request->isPost()) {
$id = input('id/d', 0);
if (!$item = MenuModel::findById($id)) {
return $this->json(4002, '记录不存在');
}
if ($item['type'] != MenuModel::TYPE_MENU) {
return $this->json(4003, '仅菜单类型可操作');
}
Db::startTrans();
try {
//自动生成常规操作
MenuModel::generate($id, $item['name'], $item['path']);
Db::commit();
return $this->json();
} catch (ValidateException $e) {
Db::rollback();
return $this->json(4001, $e->getError());
}
}
$selected = $id > 0 ? [$id] : [];
$this->data['menuList'] = $this->menuJson($selected);
return $this->view();
}
/**
* 列表
*
* @return View|Json
*/
public function index()
{
if ($this->request->isPost()) {
$menus = CmsRepository::getInstance()->getMenuList();
$menus->each(function ($item) {
if ($item['type'] == 'menu') {
$item->open = false;
}
});
$res = [
'code' => 0,
'msg' => 'success',
'count' => $menus->count(),
'data' => $menus->toArray(),
];
return json($res);
}
return $this->view();
}
/**
* xmSelect插件 json数据
*
* @param array $selected
* @param array $disabled
* @return false|string
*/
private function menuJson(array $selected = [], array $disabled = [])
{
$categoryList[] = ['title' => '顶级菜单', 'id' => 0, 'prefix' => '', 'disabled' => false, 'open' => true, 'selected' => in_array(0, $selected)];
$menus = CmsRepository::getInstance()->getMenuList();
$menus = $menus->toArray();
foreach ($menus as $k => $m) {
$menus[$k]['selected'] = in_array($m['id'], $selected);
$menus[$k]['disabled'] = in_array($m['id'], $disabled);
}
$menus = CmsRepository::getInstance()->buildMenuChild(0, $menus);
$categoryList = array_merge($categoryList, CmsRepository::getInstance()->handleSelectedList($menus));
return json_encode($categoryList, JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,270 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use app\model\Menu;
use app\model\Menu as MenuModel;
use app\model\Role as RoleModel;
use app\model\Rules;
use app\repository\CmsRepository;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Db;
use think\response\Json;
use think\response\View;
/**
* 角色管理
*
* Class Role
* @package app\controller\manager
*/
class Role extends Base
{
/**
* 删除
*
* @return Json
*/
public function del(): Json
{
if ($this->request->isPost()) {
$ids = input('post.ids/a', []);
if (empty($ids)) {
$ids[] = input('post.id/d');
}
RoleModel::deleteByIds($ids);
Log::write(get_class().'Del', 'del', '涉及到的ID为'.implode(',', $ids));
return $this->json();
}
return $this->json(4001, '非法请求!');
}
/**
* 编辑
*
* @return Json|View
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function edit()
{
$id = input('id/d', 0);
if (!$info = RoleModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title' => 'require',
]);
if ($validate !== true) {
return $validate;
}
try {
$info->save($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
$this->data['item'] = $info;
return $this->view();
}
/**
* 单个字段编辑
*
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @throws Exception
*/
public function modify(): Json
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'field' => 'require',
'value' => 'require',
]);
if ($validate !== true) {
return $validate;
}
if (!$info = RoleModel::findById($item['id'])) {
return $this->json(4001, '记录不存在');
}
$update = [$item['field'] => $item['value']];
try {
$info->save($update);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->json(4000, '非法请求');
}
/**
* 添加
*
* @return Json|View
* @throws Exception
*/
public function add()
{
if ($this->request->isPost()) {
$item = input('post.');
$validate = $this->validateByApi($item, [
'title' => 'require',
]);
if ($validate !== true) {
return $validate;
}
try {
RoleModel::create($item);
return $this->json();
} catch (ValidateException $e) {
return $this->json(4001, $e->getError());
}
}
return $this->view();
}
/**
* 角色权限
*
* @return Json|View
* @throws Exception
*/
public function rule()
{
$id = input('id/d', 0);
if (!$item = RoleModel::findById($id)) {
return $this->json(4001, '记录不存在');
}
if ($this->request->isPost()) {
$ids = input('post.ids');
$roleUpdate = $ids;//角色更新数据
$ids = explode(',', $ids);
Db::startTrans();
try {
//查询角色已有权限
$hasRules = Rules::where('ptype', 'p')->where('v0', $id)->select()->toArray();
//角色最新权限列表
$currentRules = MenuModel::where('id', 'in', $ids)->field('name')->select()->toArray();
foreach ($currentRules as &$rule) {
$route = explode(':', $rule['name']);
$v1 = $route[0];
$v2 = $route[1] ?? 'index';
$rule['ptype'] = 'p';
$rule['v0'] = $id;
$rule['v1'] = $v1;
$rule['v2'] = $v2;
}
foreach ($hasRules as $k => $has) {
foreach ($currentRules as $m => $current) {
if ($has['ptype'] == $current['ptype'] && $has['v0'] == $current['v0'] && $has['v1'] == $current['v1'] && $has['v2'] == $current['v2']) {
unset($currentRules[$m]);//删除当前权限列表已存在的 currentRules剩下的就是需要添加的记录
unset($hasRules[$k]);//删除已有权限中存在的 hasRules剩下的就是需要删除的记录
}
}
}
$insert = $currentRules;//需要添加的数据
$delete = $hasRules;//需要删除的数据
$deleteIds = array_column($delete, 'id');//需要删除的ID
(new Rules())->saveAll($insert);
(new Rules())->where('id', 'in', $deleteIds)->delete();
cache('tauthz', null);//权限缓存清空
$item->save(['rules' => $roleUpdate]);
Db::commit();
return $this->json();
} catch (ValidateException $e) {
Db::rollback();
return $this->json(4001, $e->getError());
}
}
$selected = explode(',', $item['rules']);
$this->data['authJson'] = $this->authJson($selected);
$this->data['item'] = $item;
return $this->view();
}
/**
* 构造json数据
*
* @param array $selected
* @return false|string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
private function authJson(array $selected = [])
{
$menus = Menu::field("id,pid,title,sort")
->where('status', Menu::STATUS_NORMAL)
->order('sort', 'desc')
->order('id', 'asc')
->select()->toArray();
foreach ($menus as $k => $m) {
$menus[$k]['checked'] = in_array($m['id'], $selected);
$menus[$k]['open'] = true;
}
$menus = CmsRepository::getInstance()->buildMenuChild(0, $menus);
return json_encode($menus, JSON_UNESCAPED_UNICODE);
}
/**
* 列表
*
* @return View|Json
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$page = input('page/d', 1);
$limit = input('size/d', 20);
$items = RoleModel::findList([], [], $page, $limit, function ($q) {
return $q->order('sort', 'desc')->order('id', 'asc');
});
return $this->json(0, '操作成功', $items);
}
return $this->view();
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace app\controller\manager;
use app\model\{AuthRule, AuthGroup, Log};
use app\validate\AuthRule as VAuthRule;
use Exception;
use think\exception\ValidateException;
use think\response\Json;
class Rule extends Base
{
/**
* 权限排序
* 暂不允许父级变更
*
* @return Json
* @throws Exception
*/
public function sort()
{
if ($this->request->isAjax()) {
$id = input('post.id');
$sort = input('post.sort');
$num = input('post.num/d', 1);
if($num <= 0){
$num = 1;
}
if(!in_array($sort, ['up', 'down'], true)){
return $this->json(2, '参数错误');
}
$item = AuthRule::getById($id);
if(empty($item)){
return $this->json(3, '权限不存在');
}
if($sort == 'up'){
$where = "parent_id = {$item['parent_id']} and sort < {$item['sort']}";
$order = "sort desc";
}else{
$where = "parent_id = {$item['parent_id']} and sort > {$item['sort']}";
$order = "sort asc";
}
$forSortItems = AuthRule::getListByWhereAndOrder($where, $order, $num);
if(!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for($i = 0; $i < $forSortCount; $i++){
if($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if(!empty($updateData)){
$model = new AuthRule();
$model->saveAll($updateData);
$sortStr = $sort == 'up' ? '上移' : '下调';
Log::write('rule', 'sort', "权限排序ID{$id} ,标题:{$item['title']}{$sortStr}{$num}");
return $this->json();
}
}
return $this->json(4, '无须调整排序!');
}
return $this->json(1, '非法请求!');
}
/**
* 权限删除
*/
public function del()
{
if ($this->request->isAjax()) {
$ids = input('post.ids/a');
$items = AuthRule::where('id', 'in', $ids)->select();
if(!$items){
return $this->json(1, '无此权限');
}
if(AuthRule::where('parent_id', 'in', $ids)->count()){
return $this->json(2, '当前权限有下级权限,不可删除');
}
AuthRule::destroy($ids);
AuthGroup::resetGroupRulesCache();
$ids = implode(',', $ids);
Log::write('rule', 'del', "权限删除ID{$ids}");
return $this->json();
}
return $this->json(1, '非法请求!');
}
/**
* 权限修改
*/
public function edit()
{
if($this->request->isPost()){
$item = input('post.item/a');
$id = input('post.id');
$rule = AuthRule::getById($id);
if(empty($rule)){
return $this->json(1, '请选择正确的权限');
}
$rule2 = AuthRule::getByName($item['name']);
if(!empty($rule2) && $rule2['id'] != $id){
return $this->json(2, '已存在相同权限['.$item['name'].']');
}
try {
validate(VAuthRule::class)->check($item);
AuthRule::updateById($id, $item);
AuthGroup::resetGroupRulesCache();
Log::write('rule', 'edit', "权限编辑ID{$id}, 标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(3, $e->getError());
}
}
$id = input('param.id/d');
$rule = AuthRule::getById($id);
if(empty($rule)){
return $this->json(1,'无此权限信息,请核对之后再操作!');
}else{
$this->data['item'] = $rule;
if($rule['parent_id'] > 0){
$parent = AuthRule::getById($rule['parent_id']);
$this->data['parent'] = $parent;
}
return $this->view();
}
}
/**
* 权限添加
*/
public function add()
{
if($this->request->isPost()){
$item = input('post.item/a');
try {
validate(VAuthRule::class)->check($item);
$rule = AuthRule::getByName($item['name']);
if(!empty($rule)){
return $this->json(1, '已存在相同权限');
}
$rule = AuthRule::create($item);
//基本权限的话需要重置所有已有角色权限缓存
if ($item['is_base'] > 0) {
AuthGroup::resetGroupRulesCache();
} else {
AuthGroup::resetGroupRulesCache(1);
}
Log::write('rule', 'add', "权限新增ID{$rule->id}, 标题:{$item['title']}");
return $this->json();
} catch (ValidateException $e) {
return $this->json(2, $e->getError());
}
}
$parentId = input('param.parent_id/d',0);
if($parentId > 0){
$parent = AuthRule::getById($parentId);
$this->data['parent'] = $parent;
}
$this->data['parentId'] = $parentId;
return $this->view();
}
/**
* 权限列表(全部)
*/
public function index()
{
$list = AuthRule::getListTree();
$this->data['items'] = $list;
return $this->view();
}
}

View File

@ -0,0 +1,277 @@
<?php
namespace app\controller\manager;
use app\model\Log;
use app\repository\CmsRepository;
use app\repository\OperationRepository;
use app\validate\Slide as VSlide;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\response\Json;
use think\response\View;
class Slide extends Base
{
/**
* 编辑
*
* @return Json|View
* @throws DbException
* @throws ModelNotFoundException
*/
public function edit()
{
$id = $this->request->param('id/d', 0);
if (!$slide = OperationRepository::getInstance()->findSlideById($id)) {
return $this->json(4001, '数据不存在');
}
if ($this->request->isPost()) {
$item = input('post.item/a');
$validate = new VSlide();
if (!$validate->scene('slide')->check($item)) {
return $this->json(4002, $validate->getError());
}
unset($item['id']);
OperationRepository::getInstance()->updateSlide($item, $id);
return $this->json();
}
$this->data['item'] = $slide;
$this->data['positionsJson'] = $this->xmSelectPositionsJson([$slide['position']]);
$this->data['id'] = $id;
return $this->view();
}
/**
* 添加
*
* @return View|Json
*/
public function add()
{
$repo = OperationRepository::getInstance();
if ($this->request->isPost()) {
$item = input('post.item/a');
$validate = new VSlide();
if (!$validate->scene('slide')->check($item)) {
return $this->json(4002, $validate->getError());
}
$item['type'] = $item['type'] ?? 'img';
$repo->createSlide($item);
return $this->json();
}
$this->data['positionsJson'] = $this->xmSelectPositionsJson();
return $this->view();
}
/**
* 轮播图列表
*
* @return Json|View
* @throws Exception
*/
public function index()
{
$repo = OperationRepository::getInstance();
if ($this->request->isPost()) {
$position = $this->request->param('position/s', '');
$page = $this->request->param('page/d', 1);
$size = $this->request->param('size/d', 30);
$whereMap = [];
$orders = ['sort'=>'asc'];
if (!empty($position)) {
$whereMap[] = ['position', '=', $position];
}
$list = $repo->slideList($whereMap, [], $page, $size, null, $orders);
return $this->json(0, 'success', $list);
}
$this->data['positions'] = $repo->slidePositions();
return $this->view();
}
/**
* 排序
* @return Json
*/
public function sort()
{
if (!$this->request->isPost()) {
return $this->json(4000, '非法请求');
}
try {
$id = $this->request->param('id/d', 0);
$sort = $this->request->param('sort/d', 0);
OperationRepository::getInstance()->updateSlide(['sort'=>$sort], $id);
} catch (Exception $e) {
return $this->json(4001, '排序失败');
}
return $this->json();
}
/**
* 删除
* @return Json
*/
public function del()
{
if (!$this->request->isPost()) {
return $this->json(4000, '非法请求');
}
$ids = $this->request->param('ids/a', []);
if (empty($ids)) {
$ids[] = $this->request->param('id/d', 0);
$ids = array_filter($ids);
}
if (count($ids)) {
OperationRepository::getInstance()->deleteSlides($ids);
Log::write(get_class(), 'del', '删除了轮播图涉及到的ID为'.implode(',', $ids));
}
return $this->json();
}
/**
* 显示位置下拉选项数据
*
* @param array $selected
* @param array $disabled
* @return false|string
*/
private function xmSelectPositionsJson(array $selected = [], array $disabled = [])
{
$positionList = OperationRepository::getInstance()->slidePositions();
foreach ($positionList as $k => $item) {
$positionList[$k]['selected'] = in_array($item['key'], $selected);
$positionList[$k]['disabled'] = in_array($item['key'], $disabled);
}
$positionList = CmsRepository::getInstance()->handleSelectedList($positionList);
return json_encode($positionList, JSON_UNESCAPED_UNICODE);
}
/**
* 轮播图显示位置管理
*
*/
public function position()
{
$repo = OperationRepository::getInstance();
if ($this->request->isPost()) {
$list = $repo->slidePositionList([], [], 1, 0);
return $this->json(0, 'success', $list);
}
return $this->view();
}
/**
* 添加显示位置信息
*
* @return Json|View
*/
public function addPosition()
{
$repo = OperationRepository::getInstance();
if ($this->request->isPost()) {
$item = input('post.item/a');
try {
$this->validate($item, [
'title|标题' => 'max:250',
'key|位置标识' => 'require|max:100|alphaDash'
]);
} catch (ValidateException $e) {
return $this->json(4002, $e->getError());
}
if ($repo->slidePositionExists($item['key'])) {
return $this->json(4003, '当前位置标识已存在!');
}
$repo->createSlidePosition($item);
return $this->json();
}
return $this->view();
}
/**
* 编辑显示位置信息
*
* @return Json|View
* @throws DbException
* @throws ModelNotFoundException
* @throws DataNotFoundException
*/
public function editPosition()
{
$id = $this->request->param('id/d', 0);
if (!$position = OperationRepository::getInstance()->findSlidePositionById($id)) {
return $this->json(4001, '数据不存在');
}
if ($this->request->isPost()) {
$item = input('post.item/a');
try {
$this->validate($item, [
'title|标题' => 'max:250'
]);
} catch (ValidateException $e) {
return $this->json(4002, $e->getError());
}
unset($item['id']);
unset($item['key']);
OperationRepository::getInstance()->updateSlidePosition($item, $id);
return $this->json();
}
$this->data['item'] = $position;
$this->data['id'] = $id;
return $this->view();
}
/**
* 删除显示位置信息
* @return Json
*/
public function delPosition()
{
if (!$this->request->isPost()) {
return $this->json(4000, '非法请求');
}
$ids = $this->request->param('ids/a', []);
if (empty($ids)) {
$ids[] = $this->request->param('id/d', 0);
$ids = array_filter($ids);
}
if (count($ids)) {
OperationRepository::getInstance()->deleteSlidePositions($ids);
Log::write(get_class(), 'delPosition', '删除了轮播显示位置涉及到的ID为'.implode(',', $ids));
}
return $this->json();
}
}

View File

@ -0,0 +1,239 @@
<?php
namespace app\controller\manager;
use app\service\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\ImageManagerStatic;
use app\model\{System, File};
use app\validate\Upload as VUpload;
use think\facade\{Filesystem, Config, Lang};
use think\Image as TImage;
use app\controller\BaseController;
class Upload extends BaseController
{
protected $noNeedLogin = ['video', 'file', 'image', 'wangImage'];
private $isCompress = true;
private $validate;
private $uploadPath;
private $videoUploadPath;
private $uploadPathIsWritable = 0;
private $videoUploadPathIsWritable = 0;
private $DIRECTORY_SEPARATOR = "/";
public function __construct()
{
$system = System::getSystem();
if (!empty($system)) {
$this->isCompress = $system['compress'] ?? true;
}
$this->validate = new VUpload();
$this->uploadPath = Config::get('filesystem.disks.local.url');
$this->videoUploadPath = Config::get('filesystem.disks.video.url');
$uploadDiskPath = public_path().$this->uploadPath;
$videoUploadDiskPath = public_path().$this->videoUploadPath;
if (!is_dir($uploadDiskPath)) {
@mkdir($uploadDiskPath, 0777, true);
}
if (!is_dir($videoUploadDiskPath)) {
@mkdir($videoUploadDiskPath, 0777, true);
}
if (is_writable($uploadDiskPath)) {
$this->uploadPathIsWritable = 1;
}
if (is_writable($videoUploadDiskPath)) {
$this->videoUploadPathIsWritable = 1;
}
$this->DIRECTORY_SEPARATOR = DIRECTORY_SEPARATOR == "\\" ? "/" : DIRECTORY_SEPARATOR;
ini_set('max_execution_time', '0');
ini_set("memory_limit", '-1');
set_time_limit(0);
}
//视频上传
public function video()
{
$video = request()->file('video_video');
if (!$this->videoUploadPathIsWritable) {
return $this->json(1, '上传文件夹需要写入权限');
}
if ($this->validate->checkVideo($video)) {
$md5 = $video->md5();//文件md5
if ($fileItem = File::where('md5', $md5)->find()) {
$return['src'] = $fileItem['src'];
$fileItem['updated_at'] = date('Y-m-d H:i:s');
$fileItem->save();
return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return);
}
$path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认
$hasPath = !empty($path);
// 去除以/storage/开头的部分 如/storage/20210808/test => 20210808/test
$path = ltrim(trim($path, $this->DIRECTORY_SEPARATOR), \app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR);
$datePath = $hasPath ? $path : 'videos'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录
checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath);
$src = Filesystem::putFile($datePath, $video, 'uniqid');
$src = $this->uploadPath.'/'.$src;
$return['src'] = $src;
File::add($video, $src, $md5, 'video'); //加入上传文件表
return $this->json(0, 'ok', $return);
} else {
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//文件上传(通用)
public function file()
{
$file = request()->file('file_file');
$md5 = $file->md5();//文件md5
$fileName = $file->getOriginalName();//原始文件名
if ($fileItem = File::where('md5', $md5)->find()) {
$return['src'] = $fileItem['src'];
$return['name'] = $fileName;
$fileItem['updated_at'] = date('Y-m-d H:i:s');
return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return);
}
if ($this->validate->checkFile($file)) {
try {
if (!$this->uploadPathIsWritable) {
throw new \Exception('上传文件夹需要写入权限');
}
$path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认
$hasPath = !empty($path);
$datePath = $hasPath ? $path : 'files'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录
checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath);
$src = Filesystem::putFile('files'.$this->DIRECTORY_SEPARATOR.date('Ym'), $file, 'uniqid');
$src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src;
$return['src'] = $src;
$return['name'] = $fileName;
File::add($file, $src, $md5, 'file'); //加入上传文件表
} catch (\Exception $e) {
return $this->json(1, $e->getMessage());
}
return $this->json(0, 'ok', $return);
} else {
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//图片上传(通用)
public function image()
{
// 字段名 image-image避免冲突 layui组件自动生成的隐藏file input框中name容易重名冲突
$image = request()->file('image_image');
// $img = ImageManagerStatic::cache(function($imageObj) use ($image) {
// $imageObj->make($image)->resize(300, 200)->greyscale();
// }, 10, true);
// $img = (string)ImageManagerStatic::make($image)->fit(350, 610)->encode('png');
// echo $img;
// exit;
$md5 = $image->md5();//文件md5
$path = request()->param('path/s', '');//指定路径 基于public下 若为空则默认
$hasPath = !empty($path);
if ($this->validate->checkImage($image)) {
if ($fileItem = File::where('md5', $md5)->find()) {
$return['src'] = $fileItem['src'];
$fileItem['updated_at'] = date('Y-m-d H:i:s');
return $this->json(200, '该文件已存在 路径为:'.$fileItem['path'], $return);
}
try {
if (!$this->uploadPathIsWritable) {
throw new \Exception('上传文件夹需要写入权限');
}
// 去除以/storage/开头的部分 如/storage/20210808/test => 20210808/test
if (strpos(trim($path, $this->DIRECTORY_SEPARATOR), \app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR) === 0) {
$path = substr(trim($path, $this->DIRECTORY_SEPARATOR), strlen(\app\model\Attachment::ROOT_NAME.$this->DIRECTORY_SEPARATOR));
}
$datePath = $hasPath ? $path : 'images'.$this->DIRECTORY_SEPARATOR.date('Ym');//自定义目录
$datePath = ($datePath == $this->DIRECTORY_SEPARATOR."storage") ? $this->DIRECTORY_SEPARATOR : $datePath;
checkPathExistWithMake(public_path().$this->uploadPath.'/'.$datePath);
$src = Filesystem::putFile($datePath, $image, 'uniqid');
$src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src;
$suffix = strtolower($image->getOriginalExtension());
$cutJson= input('cut');
$cutList = json_decode($cutJson, true);
// 裁剪尺寸
if (!empty($cutList)) {
$srcArr = explode('.', $src);
foreach ($cutList as $cut) {
ImageManagerStatic::make($image)->fit($cut['width'], $cut['height'])->save(public_path().$srcArr[0].'_'.$cut['name'].'.'.$suffix);
}
}
$return['src'] = $src;
File::add($image, $src, $md5); //加入上传文件表
} catch (\Exception $e) {
return $this->json(1, $e->getMessage());
}
return $this->json(0, 'ok', $return);
} else {
$errorMsg = Lang::get($this->validate->getError());
return $this->json(1, $errorMsg);
}
}
//富文本编辑器商城图片
public function wangImage()
{
$imageArr = request()->file('wang_img'); // 该方式前端js上传方法中字段名称必须以数组形式传参 如 wang_img[] = 值
$errno = 0;
$data = [];
if (!$this->uploadPathIsWritable) {
$errno = 1;
$data[] = '上传文件夹需要写入权限';
} else {
foreach ($imageArr as $image) {
$md5 = $image->md5();//文件md5
if ($fileItem = File::where('md5', $md5)->find()) {
$return['src'] = $fileItem['src'];
$fileItem['updated_at'] = date('Y-m-d H:i:s');
$data[] = $fileItem['src'];
$fileItem->save();
continue;
}
if ($this->validate->checkImage($image)) {
checkPathExistWithMake(public_path().$this->uploadPath.'/images/'.date('Ym'));
$src = Filesystem::putFile('images/'.date('Ym'), $image, 'uniqid');
$src = $this->uploadPath.$this->DIRECTORY_SEPARATOR.$src;
$data[] = $src;
if ($this->isCompress) {
Image::resize($src);
}
File::add($image, $src, $md5); //加入上传文件表
} else {
$errno = 1;
$data = [];
$data[] = Lang::get($this->validate->getError());
break;
}
}
}
$return['errno'] = $errno;
$return['data'] = $data;
return json($return);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace app\controller\manager;
/**
* 微信公众号配置
* Class Wechat
* @package app\controller\manager
*/
class Wechat extends Base
{
// 公众号设置
public function base()
{
// TODO
return 'TODO ...';
}
// 公众号设置
public function menu()
{
// TODO
return 'TODO ...';
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace app\controller\manager;
class map extends Base
{
/**
* 商家列表列表
*
* @return Json|View
* @throws Exception
*/
public function index()
{
return $this->view();
}
}

16
app/event.php Normal file
View File

@ -0,0 +1,16 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [],
];

View File

@ -0,0 +1,10 @@
<?php
namespace app\exception;
use Exception;
class ApiRedirectException extends Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace app\exception;
class RepositoryException extends \Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace app\exception;
class TraitException extends \Exception
{
}

37
app/job/NotifySms.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace app\job;
use app\repository\AccountRepository;
use think\queue\Job;
class NotifySms
{
public function fire(Job $job, $data){
//短信通知列表
if ($data) {
foreach ($data as $item) {
echo sprintf("短信发送成功phone:%s time:%s \n", $item['mobile'], date('Y-m-d H:i:s'));
sleep(mt_rand(1,3));
}
}
// if ($job->attempts() > 3) {
// //通过这个方法可以检查这个任务已经重试了几次了
// echo sprintf('发送短信失败过多');
// }
//如果任务执行成功后 记得删除任务不然这个任务会重复执行直到达到最大重试次数后失败后执行failed方法
$job->delete();
// 也可以重新发布这个任务
// $job->release(3); //$delay为延迟时间
}
public function failed($data){
// ...任务达到最大重试次数后,失败了
}
}

10
app/middleware.php Normal file
View File

@ -0,0 +1,10 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
//\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
];

View File

@ -0,0 +1,35 @@
<?php
namespace app\middleware;
use Closure;
use app\service\Jwt as JwtService;
/**
* API登录认证需要先调用JWT解析用户信息
* Class ApiLogin
* @package app\middleware
*/
class ApiLogin
{
public function handle($request, Closure $next) {
$authorization = $request->authorization ?? '';
if (empty($authorization)) {
return json(['code' => 6001, 'msg' => '请填写token']);
}
if (!JwtService::validate($authorization)) {
return json(['code' => 6001, 'msg' => 'token验证失败或已失效']);
}
$userInfo = $request->user ?? [];
if (!isset($userInfo['user_id']) || empty($userInfo['user_id'])) {
return json(['code' => 6001, 'msg' => 'token已失效']);
}
// 自定义过期时间校验。
if(isset($userInfo['expire_time']) && time() >= $userInfo['expire_time']) {
return json(['code' => 6001, 'msg' => 'token已失效']);
}
return $next($request);
}
}

50
app/middleware/Auth.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace app\middleware;
use Closure;
use app\model\AuthRule;
use tauthz\facade\Enforcer;
use think\facade\Cache;
class Auth
{
public function handle($request, Closure $next)
{
$auth = session('auth');
if (!$auth) {
return redirect(url('manager.login/index'));
}
$module = 'manager';
$controller = unCamelize(request()->controller());
$controller = str_replace($module.'.', '', $controller);
$controller = str_replace('.', '/', $controller);//兼容多层级目录 如 /manager/test/article/index
$action = unCamelize(request()->action());
$roles = Enforcer::getRolesForUser($auth['user_id']);
// $per = Enforcer::getPermissionsForUser($roles[0]);
// var_dump($controller);
// var_dump($action);
// var_dump($roles);
// var_dump($per);
// exit;
// return $next($request);//暂时停用权限校验
// var_dump($controller);
// var_dump($action);
// var_dump(Enforcer::hasPermissionForUser(1, $controller, 'group-make'));exit;
foreach ($roles as $role) {
// TODO 关注批量权限检测是否可用
//只需要有一个角色具有权限就放通 此处第一个参数不是用户 而是 角色 此方法是检测用户|角色是否具有某个权限的公用方法
if (Enforcer::hasPermissionForUser($role, $controller, $action)) {
return $next($request);
}
}
if (request()->isAjax()) {
return json(['code' => 4001, 'msg' => '没有权限']);
} else {
return view('/manager/error/jump')->assign('msg', '很抱歉,您还没有权限,请联系管理员开通!');
}
}
}

56
app/middleware/Csrf.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace app\middleware;
use Closure;
use think\Request;
/**
* CSRF校验
*/
class Csrf
{
public function handle(Request $request, Closure $next)
{
if($request->isPost()){
$check = $request->checkToken();
if(false === $check) {
// return $this->csrfError($request);
}
}
return $next($request);
}
protected function csrfError($request, $msg = '非法请求, 用户身份认证失败!')
{
if($request->isAjax()) {
return json(['code' => 401, 'msg' => $msg], 200);
} else {
$referer = $_SERVER['HTTP_REFERER'] ?? null;
if (empty($referer)) {
$url = '/';
} else {
$domain = $request->domain();
$urlInfo = parse_url($referer);
$scheme = $urlInfo['scheme'] ?? '';
$requestSrc = '';
if (!empty($scheme)) {
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
}
if($domain != $requestSrc) {
$url = '/';
} else {
$url = 'javascript:history.back(-1);';
}
}
$errorData = [
'code'=> 401,
'msg' => $msg,
'data' => [],
'wait' => 5,
'url' => $url
];
return view('error/400', $errorData);
// 返回401视图 response type has html、json、jsonp、xml、file、view、redirect
}
}
}

37
app/middleware/JWT.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace app\middleware;
use Closure;
use app\service\Jwt as JwtService;
/**
* 根据TOKEN解析用户信息
*
* Class JWT
* @package app\middleware
*/
class JWT
{
public function handle($request, Closure $next)
{
$authorization = $request->header('Authorization');
$tokenStr = $request->param('token/s', '');
if ($authorization) {
$authorization = str_replace('Bearer ', '', $authorization);
}
//优先取header中token
$token = $authorization ?: $tokenStr;
$userInfo = [];
if (!empty($token)) {
$userInfo = JwtService::parse($token);//token中携带的简易用户信息
}
$request->user = $userInfo;
// authorization用于移交ApiLogin认证
$request->authorization = $token;
return $next($request);
}
}

26
app/middleware/Log.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace app\middleware;
use Closure;
use think\Request;
/**
* 日志记录
*
* Class Log
* @package app\middleware
*/
class Log
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// 添加中间件执行代码
\app\model\Log::write($request->controller(), $request->action(), $request->pathinfo(), $request->method());
return $response;
}
}

25
app/middleware/Login.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace app\middleware;
use Closure;
use think\Request;
/**
* 前台登录验证
*
* Class Login
* @package app\middleware
*/
class Login
{
public function handle(Request $request, Closure $next)
{
if (!session('frontend_auth')) {
$url = $request->url(true);
return redirect(url('login/index').'?url='.urlencode($url));
}
return $next($request);
}
}

11
app/model/Account.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace app\model;
use think\model;
use think\model\relation\BelongsToMany;
use think\model\relation\HasOne;
class Account extends Base
{
}

90
app/model/Archives.php Normal file
View File

@ -0,0 +1,90 @@
<?php
namespace app\model;
use app\exception\RepositoryException;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
class Archives extends Base
{
public const ORIGINAL_TABLE = 'bee_archives';
public const STATUS_NORMAL = 1;//正常
public const STATUS_DISABLE = 0;//禁用
//根据栏目ID获取文章分页列表
public static function getListPageByCategory($categoryId, $per = 20, $keyword = '')
{
$where = [
['category_id', '=', $categoryId],
];
$param = [];
//$param['category_id'] = $categoryId;
if ($keyword != '') {
$where[] = ['title', 'like', '%' . $keyword . '%'];
$param['keyword'] = $keyword;
}
$paginate = [
'list_rows' => $per,
'query' => $param
];
return self::with(["archivesCategory"])->where($where)
->order(["sort"=>"desc"])
->paginate($paginate, false);
}
/**
* 创建人信息
*
* @return HasOne
*/
public function member(): HasOne
{
return $this->hasOne(Member::class, 'id', 'created_by')->bind(['nickname']);
}
/**
* 栏目 后台用
*
* @return HasOne
*/
public function category(): HasOne
{
return $this->hasOne(ArchivesCategory::class, 'id', 'category_id')->bind(['category' => 'title']);
}
/**
* 栏目
* @return HasOne
*/
public function archivesCategory(): HasOne
{
return $this->hasOne(ArchivesCategory::class, 'id', 'category_id');
}
public static function onAfterInsert( $archives)
{
$archives->sort = ceil(self::max("sort")+1);
$archives->save();
}
public static function onBeforeUpdate( $archives)
{
//检查sort
$hasFlag = self::where([
["sort","=",$archives->sort],
["id","<>",$archives->id]
])->find();
if(!empty($hasFlag)){
throw new RepositoryException('sort不能重复,保留小数点后2位');
}
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace app\model;
use think\Collection;
use think\db\exception\DbException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\model\relation\HasOne;
class ArchivesCategory extends Base
{
const index_id = 1;//首页
/**
* 检测数据下 是否有子栏目
*
* @param array $ids
* @return bool
*/
public static function hasChildrenByIds(array $ids): bool
{
return self::whereIn('pid', $ids)->count() > 0;
}
/**
* 检测数据下 是否有子内容
*
* @param array $ids
* @return bool
*/
public static function hasContentByIds(array $ids): bool
{
return Archives::whereIn('category_id', $ids)->count() > 0;
}
/**
* 获取列表
*
* @return ArchivesCategory[]|array|Collection
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function getList()
{
return self::field('id,pid,title,name,sort,path,true as open')
->order('sort', 'desc')
->order('id', 'asc')
->select();
}
/**
* 模型
*
* @return HasOne
*/
public function model(): HasOne
{
return $this->hasOne(ArchivesModel::class, 'id', 'model_id')->bind(['model' => 'name']);
}
//--王兴龙 auth start --------------------------------------------------------------------------------------------------
//获取前台菜单
public static function getListForFrontMenu()
{
$items = self::withJoin(["model"])
->order(['sort' =>'desc',"id"=>"asc"])
->select()
->toArray();
return self::getCates($items);
}
//获取递归栏目
public static function getCates($cates, $parentId = 0)
{
$menus = [];
foreach ($cates as $cate) {
if ($cate['pid'] == $parentId) {
$children = self::getCates($cates, $cate['id']);
if (!empty($children)) {
$cate['children'] = $children;
}
$menus[] = $cate;
}
}
return $menus;
}
//当前分类的最高级分类Id
public static function firstGradeById($id)
{
$res = 0;
$item = self::getById($id);
if ($item) {
$res = $id;
if ($item['pid'] > 0) {
$items = self::select()->toArray();
$first = self::getFirstGrade($items, $item['pid']);
if (!empty($first)) {
$res = $first['id'];
}
}
}
return $res;
}
public static function getFirstGrade($items, $parentId)
{
$data = [];
foreach ($items as $key => $item) {
if ($item['id'] == $parentId) {
if ($item['pid'] > 0) {
unset($items[$key]);
$parent = self::getFirstGrade($items, $item['pid']);
if (!empty($parent)) {
$data = $parent;
} else {
$data = $item;
}
} else {
$data = $item;
}
}
}
return $data;
}
/**
* 获取每个栏目文章封面大小 数组
* */
public static function getArchiveCoverSizeArr()
{
return json_encode(self::column(["id","archive_cover_size"],"id"));
}
/**
* 获取每个栏目 设置路由
* */
public static function getRoute()
{
return self::column(["id","name","route"],"id");
}
//--王兴龙 auth end --------------------------------------------------------------------------------------------------
}

View File

@ -0,0 +1,19 @@
<?php
namespace app\model;
/**
* 文档模型
* Class ArchivesModel
* @package app\model
*/
class ArchivesModel extends Base
{
public const MODEL_ARCHIVES = 'archives'; // 文章
static function allModel()
{
return [
self::MODEL_ARCHIVES=>"1",
];
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace app\model;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
/**
* 文档模型字段
* Class ArchivesModelField
* @package app\model
*/
class ArchivesModelField extends Base
{
public const VIRTUAL_YES = 1;//虚拟字段 是
public const VIRTUAL_NO = 0;//虚拟字段 否
/**
* 模型添加字段列表
*
* @param int $modelId
* @param string $modelName
* @throws Exception
*/
public static function setFieldList(int $modelId, string $modelName)
{
if (self::where('model_id', $modelId)->count() <= 0) {
$rs = Db::query("SHOW FULL COLUMNS FROM ".Archives::ORIGINAL_TABLE);
$insert = [];
foreach ($rs as $val) {
$arr = [];
$arr['model_id'] = $modelId;
$arr['model'] = $modelName;
$arr['name'] = $val['Field'];
$arr['title'] = $val['Comment'];
$arr['remark'] = $val['Comment'];
$insert[] = $arr;
}
(new self())->saveAll($insert);
}
}
/**
* 同步字段
*
* @param int $modelId
* @param string $modelName
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function syncFieldList(int $modelId, string $modelName)
{
$rs = Db::query("SHOW FULL COLUMNS FROM ".Archives::ORIGINAL_TABLE);
$oldFieldList = self::where('model_id', $modelId)->where('is_virtual', self::VIRTUAL_NO)->select();
$oldFields = $oldFieldList->column('name');
$newestFields = [];
foreach ($rs as $val) {
$newestFields[] = $val['Field'];
}
//待删除字段
$delete = array_diff($oldFields, $newestFields);
//待新增字段
$needInsertFields = array_diff($newestFields, $oldFields);
$insert = [];//新增字段
foreach ($rs as $val) {
if (in_array($val['Field'], $needInsertFields)) {
$arr = [];
$arr['model_id'] = $modelId;
$arr['model'] = $modelName;
$arr['name'] = $val['Field'];
$arr['title'] = $val['Comment'];
$arr['remark'] = $val['Comment'];
$insert[] = $arr;
}
}
(new self())->saveAll($insert);
(new self())->whereIn('name', $delete)->delete();
}
/**
* 各栏目[模型] 可展示字段列表
*
* @return array
*/
public static function showFieldList(): array
{
$list = self::alias('amf')
->leftJoin('archives_category ac', 'ac.model_id = amf.model_id')
->field('amf.*, ac.id as category_id, ac.title as category_title')
->where('amf.status', self::COMMON_ON)
->where('ac.id', '>', 0)
->select();
$res = [];
$list = $list->toArray();
foreach ($list as $item) {
if (!isset($res[$item['category_id']])) {
$res[$item['category_id']] = [];
}
$res[$item['category_id']][] = $item['name'];
}
return $res;
}
}

69
app/model/Attachment.php Normal file
View File

@ -0,0 +1,69 @@
<?php
namespace app\model;
use Exception;
use think\exception\ValidateException;
class Attachment extends Base
{
protected $name = 'file';
public const TYPE_DIR = 'dir';//目录
public const TYPE_IMG = 'image';//图片
public const TYPE_VIDEO = 'video';//视频
public const TYPE_FILE = 'file';//文件
public const ROOT_NAME = 'storage';//根目录名称
public const ROOT_PATH = "/".self::ROOT_NAME;//文件存放根路径
public static function list(): array
{
$where[] = ['src', 'like', '%'.self::ROOT_PATH."/"];
return self::findList($where);
}
/**
* 路径目录处理 会逐级检查路径上所有目录是否存在 不存在的目录全部创建[创建到数据库]
*
* @throws Exception
*/
public static function pathDirHandle(string $path): bool
{
$pathInfo = pathinfo($path);
$fullPath = isset($pathInfo['extension']) ? $pathInfo['dirname'] : $pathInfo['dirname']."/".$pathInfo['filename'];
// 全路径 如 /storage/dir1/dir2/dir3/dir4/ 注意前后都有/
$fullPath = $fullPath."/";
$pathArr = explode("/", trim($fullPath, "/"));
$insert = [];
$now = date('Y-m-d H:i:s');
try {
// 检测全路径上所有目录是否存在 不存在则创建
foreach ($pathArr as $k => $p) {
$currentPath = '/';
$currentArr = array_slice($pathArr, 0, $k);
if ($currentArr) {
$currentPath = "/".implode('/', $currentArr)."/";
}
if ($currentPath != '/' && self::where('path', $currentPath)->where('name', $p)->count() <= 0) {
$arr = [];
$arr['path'] = $currentPath;
$arr['name'] = $p;
$arr['created_at'] = $now;
$arr['is_dir'] = self::COMMON_ON;
$arr['type'] = self::TYPE_DIR;
$insert[] = $arr;
}
}
(new self())->saveAll($insert);
return true;
} catch (Exception $e) {
return false;
}
}
}

390
app/model/Base.php Normal file
View File

@ -0,0 +1,390 @@
<?php
namespace app\model;
use Exception;
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Model;
use think\Paginator;
class Base extends Model
{
protected $autoWriteTimestamp = false;
// 布尔值数字关系
public const BOOL_FALSE = 0;
public const BOOL_TRUE = 1;
public const COMMON_ON = 1;
public const COMMON_OFF = 0;
//根据Id列表获取列表
public static function getListByIds($ids)
{
if (count($ids) == 0 || empty($ids)) {
return [];
}
return self::where('id', 'in', $ids)->select()->toArray();
}
//根据ID获取单条数据
public static function getById($id)
{
if ($id <= 0) {
return [];
}
return self::where('id', $id)->findOrEmpty()->toArray();
}
/**
* 通过ID查找记录
*
* @param int $id
* @param array $fields
* @param callable|null $call
* @return array|Model|null
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function findById(int $id, array $fields = [], callable $call = null)
{
$q = self::when(!empty($fields), function ($q) use ($fields) {
$q->field($fields);
});
if ($call !== null) {
$q = $call($q);
}
return $q->find($id);
}
/**
* 查找单个记录
*
* @param array $where
* @param array $fields
* @param callable|null $call
* @return array|Model|null
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function findOne(array $where = [], array $fields = [], callable $call = null)
{
$q = self::when(!empty($fields), function ($q) use ($fields) {
$q->field($fields);
})->where($where);
if ($call !== null) {
$q = $call($q);
}
return $q->find();
}
//根据ID更新数据
public static function updateById($id, $data)
{
return self::where('id', $id)->update($data);
}
//根据where条件和排序获取记录
public static function getListByWhereAndOrder($where, $order, $limit = 1)
{
return self::where($where)
->order($order)
->limit($limit)
->select()
->toArray();
}
/**
* 根据ID删除数据
*
* @param int $id
* @return bool
*/
public static function deleteById(int $id): bool
{
return self::where('id', $id)->delete();
}
/**
* 根据ID列表删除数据
*
* @param array $ids
* @return bool
*/
public static function deleteByIds(array $ids): bool
{
return self::whereIn('id', $ids)->delete();
}
/**
* 排序
*
* @param int $id 调整ID
* @param string $type 本次操作类型 向上、向下
* @param int $num 移动位数
* @param string $listType 列表的排序方式 默认为降序
* @param array $where 额外条件 如限制在指定分类下 where[] = ['category_id', '=', 6]
* @return array
* @throws Exception
*/
public static function sort(int $id, string $type, int $num = 1, string $listType = 'desc', array $where = []): array
{
$res = ['code' => 0, 'msg' => 'success'];
$item = self::getById($id);
if (!$item) {
$res['code'] = 1;
$res['msg'] = '记录不存在';
return $res;
}
if ($listType == 'desc') {
if ($type == 'down') {
$where[] = ['sort', '<', $item['sort']];
$order = "sort desc";
} else {
$where[] = ['sort', '>', $item['sort']];
$order = "sort asc";
}
} else {
if ($type == 'up') {
$where[] = ['sort', '<', $item['sort']];
$order = "sort desc";
} else {
$where[] = ['sort', '>', $item['sort']];
$order = "sort asc";
}
}
$forSortItems = self::getListByWhereAndOrder($where, $order, $num);
if (!empty($forSortItems)) {
$updateData = [];
$forSortCount = count($forSortItems);
for ($i = 0; $i < $forSortCount; $i++) {
if ($i == 0) {
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
} else {
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if (!empty($updateData)) {
$obj = new static();
$obj->saveAll($updateData);
return $res;
}
}
$res['code'] = 1;
$res['msg'] = '无需调整';
return $res;
}
/**
* 查询列表 [带分页 适用于后台]
*
* @param array $data 查询数据 格式如下
* [
* 'fields' => ['id','title','desc'],//查询字段
* 'where' => [
* ['name', 'like', '%thinkphp%'],
* ['title', 'like', '%thinkphp%'],
* ['id', '>', 0],
* ['status', '=', 1],
* ],//查询条件
* 'order' => ['order'=>'desc','id'=>'desc'],//排序
* 'size' => 50,//每页数量
* ]
* @param array $pageParams 分页参数 具体参考https://www.kancloud.cn/manual/thinkphp6_0/1037638
* @param callable|null $callback 复杂查询条件 使用闭包查询 此时建议data留空
* @return Paginator
* @throws DbException
*/
public static function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator
{
$q = new static();
$fields = isset($data['fields']) && !empty($data['fields']) ? $data['fields'] : [];
$where = isset($data['where']) && !empty($data['where']) ? $data['where'] : [];
$order = isset($data['order']) && !empty($data['order']) ? $data['order'] : [];
$limit = $data['size'] ?? 20;
if (count($where)) {
$q = $q->where($where);
}
if (count($fields)) {
$q = $q->field($fields);
}
if ($callback) {
$q = $callback($q);
}
if (count($order)) {
$q = $q->order($order);
}
$pageParams['list_rows'] = $limit;
return $q->paginate($pageParams);
}
/**
* 查询列表
*
* @param array $simpleWhere 简易查询条件
* @param array $fields 查询字段 []表示全部
* @param int $page 默认第一页 0不限制
* @param int $limit 限制条数 0不限制
* @param callable|null $callback 复杂的条件 使用闭包查询
* @param array $orders 键值对,排序
* @return array
* @throws Exception
*/
public static function findList(array $simpleWhere = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $orders = []): array
{
$q = new static();
$data = [
'total' => 0,
'current' => $page,
'size' => $limit,
'list' => new Collection(),
];
if (count($fields)) {
$q = $q->field($fields);
}
if (count($simpleWhere)) {
$q = $q->where($simpleWhere);
}
if ($callback) {
$q = $callback($q);
}
$data['total'] = $q->count();
if ($data['total']) {
if (count($orders)) {
$q = $q->order($orders);
}
if ($page) {
$q = $q->page($page);
}
if ($limit == 0) {
$q = $q->limit(1000);
} else {
$q = $q->limit($limit);
}
$data['list'] = $q->select();
}
return $data;
}
/**
* 获取路径
*
* @param int $pid
* @return string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function getPath(int $pid): string
{
if ($pid == 0) {
$path = ',0,';
} else {
$parent = self::findById($pid);
if (empty($parent)) {
$path = ',0,';
} else {
$path = $parent['path'].$parent['id'].',';
}
}
return $path;
}
/**
* 刷新路径
*
* @param int $pid
* @param array $data 默认全部 若data不为空 至少包含[id,path, $pidField]
* @param string $pidField 父级ID字段名 默认 pid
* @throws Exception
*/
public static function refreshPath(int $pid = 0, array $data = [], string $pidField = 'pid')
{
$data = !empty($data) ? $data : self::column('id,path,'.$pidField);
$updateAllPaths = [];
self::recursionPath($pid, $data, $updateAllPaths);
(new static())->saveAll($updateAllPaths);
}
/**
* 获取递归最新路径
*
* @param int $pid
* @param array $data 全部数据 尽量传全部数据
* @param array $paths
*/
public static function recursionPath(int $pid, array $data, array &$paths = [])
{
foreach ($data as $k => $v) {
if ($pid == $v['pid']) {
$arr = [];
$arr['id'] = $v['id'];
if ($pid == 0) {
$arr['path'] = ',0,';
} else {
$arr['path'] = $paths[$v['pid']]['path'].$v['pid'].',';
}
$paths[$v['id']] = $arr;
unset($data[$k]);
self::recursionPath($v['id'], $data, $paths);
}
}
}
/**
* 获取所有后代ID[包含孙级] 仅对拥有path字段的表生效
*
* @param int $id
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function getAllChildrenIds(int $id): array
{
$item = self::find($id);
if ($item && isset($item['path'])) {
$path = $item['path'].$id.',';
return self::where('path', 'like', $path.'%')->column('id');
} else {
return [];
}
}
}

147
app/model/Block.php Normal file
View File

@ -0,0 +1,147 @@
<?php
namespace app\model;
use think\model\relation\HasOne;
class Block extends Base
{
const BLOCK = 1;//富文本
const TEXT= 2;//纯文本
const IMG = 3;//图片
const GROUP = 4;//多图
const FILE = 5;// 文件
const VIDEO = 6;// 视频
const ING_LIST = 7;// 图文列表
//获取类型键值对
public static function getTypes()
{
return [
self::BLOCK => '富文本',
self::TEXT => '纯文本',
self::IMG => '图片',
self::GROUP => '多图',
self::FILE => '文件',
self::VIDEO => '视频',
self::ING_LIST => '图文列表',
];
}
//根据键值和类目获取数据
public static function getByKeyword($keyword, $categoryId)
{
return self::where('keyword', $keyword)->where('category_id', $categoryId)->findOrEmpty()->toArray();
}
//根据栏目ID获取块列表
public static function getByCategoryId($categoryId)
{
if($categoryId <= 0){
return [];
}
$items = self::where('category_id', $categoryId)
->order('id desc')
->select()
->toArray();
if(empty($items)){
return [];
}
$data = [];
foreach($items as $item){
$data[$item['name']] = $item;
}
return $data;
}
//获取在使用中的文件
public static function getFilesInUse()
{
$items = self::whereIn('type', [self::IMG, self::GROUP, self::VIDEO, self::TEXT])
->select()
->toArray();
$data = [];
foreach($items as $item){
if($item['type'] == self::IMG){
$file = trim($item['value']);
if(!empty($file)){
$key = getKeyByPath($file);
$data[$key] = $file;
}
}elseif($item['type'] == self::GROUP){
$val = trim($item['value']);
if (!empty($val) && $val != '[]' && $val != 'null') {
$files = json_decode($val, true);
foreach($files as $file){
$src = trim($file['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
}
}elseif($item['type'] == self::VIDEO){
$video = trim($item['value']);
if(!empty($video)){
$key = getKeyByPath($video);
$data[$key] = $video;
}
$img = trim($item['img']);
if(!empty($img)){
$key = getKeyByPath($img);
$data[$key] = $img;
}
}elseif($item['type'] == self::TEXT){
$imgs = getImageUrlFromText($item['value']);
if(!empty($imgs)){
$data = array_merge($data, $imgs);
}
$videos = getVideoUrlFromText($item['value']);
if(!empty($videos)){
$data = array_merge($data, $videos);
}
}
}
return $data;
}
/**
* 栏目
*
* @return HasOne
*/
public function category(): HasOne
{
return $this->HasOne(ArchivesCategory::class, 'id', 'category_id');
}
/**
* 设置内容
* */
public function setContentAttr($value,$data)
{
if($data['type'] == self::ING_LIST){
$content = array_column($value,null,"sort");
$contentKey = array_keys($content);
sort($contentKey);
$contentData =[];
foreach ($contentKey as $ck){
$contentData[]= $content[$ck];
}
return json_encode($contentData);
}
return $value;
}
/**
* 设置内容
* */
public function getContentAttr($value,$data)
{
if($data['type'] == self::ING_LIST){
return json_decode($value,true)
}
return $value;
}
}

19
app/model/Config.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace app\model;
/**
* 配置表
* Class Config
* @package app\model
*/
class Config extends Base
{
// 路径都没有加前缀/ 需要的地方自行添加
// 小程序内容详情路径----新闻内容详情 包含: 关于我们模型、普通文章模型
public const MINI_PATH_ARCHIVES = 'pagesB/articleDetail/articleDetail';
// 小程序病种详情路径----病种内容详情(前端叫法) 包含: 非我们模型、普通文章模型的其他模型 如问题文章、科普视频等
public const MINI_PATH_PROBLEM = 'pagesB/problemDetail/problemDetail';
// 小程序预约列表界面
public const MINI_PATH_APPOINTMENT = 'pagesA/makeAnPppointment/makeAnPppointment';
}

12
app/model/ConfigGroup.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace app\model;
/**
* 配置分组表
* Class ConfigGroup
* @package app\model
*/
class ConfigGroup extends Base
{
}

24
app/model/Feedback.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace app\model;
use think\model\relation\HasOne;
/**
* 意见与反馈
*
* Class Feedback
* @package app\model
*/
class Feedback extends Base
{
/**
* 关联提交者
*
* @return HasOne
*/
public function account(): HasOne
{
return $this->hasOne(Account::class, 'id', 'account_id');
}
}

129
app/model/File.php Normal file
View File

@ -0,0 +1,129 @@
<?php
namespace app\model;
use app\service\AliOss;
use Exception;
use think\facade\Config;
use think\Image;
class File extends Base
{
const IMG = 'image';
const VIDEO = 'video';
const FILE = 'file';
//获取文件类型
public static function getTypes()
{
return [
'image' => '图片',
'video' => '视频',
'file' => '文件'
];
}
//获取文件列表
public static function getList($type = '', $page = 1, $per = 20)
{
$limit = ($page - 1) * $per;
if ($type != '') {
if (!in_array($type, array_keys(self::getTypes()))) {
return [];
}
$items = self::where('type', $type)
->order('id desc');
} else {
$items = self::order('id desc');
}
$items = $items->limit($limit, $per)->select()->toArray();
foreach ($items as &$item) {
$item['sizeStr'] = sizeToStr($item['size']);
}
return $items;
}
//获取分页列表
public static function getListPage($type = '', $per = 20)
{
if ($type != '') {
if (!in_array($type, array_keys(self::getTypes()))) {
return [];
}
return self::where('type', $type)
->order('id desc')
->paginate([
'list_rows' => $per,
'query' => [
'type' => $type
]
], false);
} else {
return self::order('id desc')
->paginate([
'list_rows' => $per
], false);
}
}
//添加,$w_h图片尺寸大小单位像素只对type=img时有效
public static function add($file, $src, $md5, $type = 'image')
{
$realPath = public_path().ltrim($src, '/');
$oss = false;
if (is_file($realPath) && $type == 'image') {
$img = Image::open($realPath);
list($w, $h) = $img->size();
$w_h = $w.'px * '.$h.'px';
} else {
$w_h = '';
}
$now = date('Y-m-d H:i:s');
Attachment::pathDirHandle($src);
Config::load('extra/base', 'base');
$baseConfig = config('base');
if (isset($baseConfig['oss']) && $baseConfig['oss'] == 'true') {
$ossObject = AliOss::instance();
try {
$pathInfo = pathinfo($src);
$ossConfig = AliOss::config();
$bucket = $ossConfig['bucket'];
//是否存在
if (!$ossObject->doesObjectExist($bucket, ltrim($src, '/'))) {
//创建目录
$ossObject->createObjectDir($bucket, ltrim($pathInfo['dirname'], '/'));
$ossObject->uploadFile($bucket, ltrim($src, '/'), $realPath);
}
$oss = true;
} catch (Exception $e) {
\think\facade\Log::error('阿里云OSS上传文件失败 '.$e->getMessage());
}
}
// 将src中路径创建
return self::create([
'type' => $type,
'name' => $file->getOriginalName(),
'md5' => $md5,
'src' => $src,
'path' => isset(pathinfo($src)['dirname']) ? pathinfo($src)['dirname'].'/' : '',
'size' => $file->getSize(),
'suffix' => $file->getOriginalExtension(),
'mime_type' => $file->getOriginalMime(),
'created_at' => $now,
'updated_at' => $now,
'is_oss' => $oss,
'w_h' => $w_h
]);
}
//获取所有记录
public static function getAll()
{
return self::select()->toArray();
}
}

39
app/model/Link.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace app\model;
class Link extends Base
{
public static function delByIds($ids)
{
return self::whereIn('id', $ids)->delete();
}
//获取友情链接
public static function getList()
{
return self::order('sort asc')
->select()
->toArray();
}
public static function onAfterInsert($item)
{
$item->sort = $item->id;
$item->save();
}
//获取友情链接涉及到的文件
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach($items as $item){
$src = trim($item['src']);
if(!empty($src)){
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
return $data;
}
}

41
app/model/Log.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace app\model;
use think\model\relation\HasOne;
class Log extends Base
{
//记录操作日志
public static function write($controller, $action, $content, $requestType = '')
{
$auth = session('auth');
return self::create([
'member_id' => $auth['user_id'] ?? 0,
'name' => $auth['username'] ?? 0,
'ip' => request()->ip(),
'create_time' => time(),
'controller' => $controller,
'request_type' => $requestType,
'action' => $action,
'content' => $content
]);
}
/**
* @return HasOne
*/
public function memberName(): HasOne
{
return $this->hasOne(Member::class, 'id', 'member_id')->bind(['operator' => 'nickname']);
}
public function getCreateTimeAttr($value)
{
if (empty($value)) {
return $value;
}
return date('Y-m-d H:i:s', $value);
}
}

6
app/model/LoginLog.php Normal file
View File

@ -0,0 +1,6 @@
<?php
namespace app\model;
class LoginLog extends Base
{
}

59
app/model/Member.php Normal file
View File

@ -0,0 +1,59 @@
<?php
namespace app\model;
class Member extends Base
{
public const STATUS_NORMAL = 1;//正常
public const STATUS_DISABLE = 0;//禁用
public static function getList($limit = 40)
{
return self::alias('m')
->leftjoin('auth_group g', 'g.id=m.group_id')
->field('m.id,m.username,m.login_time,m.group_id,g.title')
->order('m.id', 'asc')
->paginate($limit);
}
/**
* 根据角色分组返回用户
* @param int $groupId 角色分组ID
* @param int $limit 每页数量
*/
public static function getListByGroup($groupId, $limit = 40)
{
return self::alias('m')
->leftjoin('auth_group g', 'g.id=m.group_id')
->field('m.id,m.username,m.login_time,m.group_id,g.title')
->where('m.group_id', '=', $groupId)
->order('m.id', 'asc')
->paginate($limit);
}
//根据用户名获取管理账号
public static function getByUserName($username)
{
return self::where('username', trim($username))
->findOrEmpty()
->toArray();
}
//根据ID获取管理账户和相关权限
public static function getMemberAndRulesByID($memberId)
{
return self::alias('m')
->join('auth_group g', 'm.group_id = g.id', 'LEFT')
->field('m.group_id,g.rules')
->where('m.id', $memberId)
->findOrEmpty()
->toArray();
}
public static function updateCates($id, $cates)
{
$cates = implode(',', $cates);
$data = ['cates' => $cates];
self::updateById($id, $data);
}
}

71
app/model/Menu.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace app\model;
use Exception;
class Menu extends Base
{
public const SHOW_YES = 1;
public const SHOW_NO = 0;
public const STATUS_NORMAL = 1;
public const STATUS_DISABLE = 0;
public const TYPE_MENU = 'menu';
public const TYPE_ACTION = 'action';
/**
* 默认操作
*
* @return string[]
*/
public static function defaultAction(): array
{
return [
'index' => '查看',
'add' => '添加',
'edit' => '编辑',
'del' => '删除',
'sort' => '排序',
'modify' => '属性设置',
];
}
/**
* 自从生成常规操作权限
*
* @param int $id
* @param string $name
* @param string $path
* @throws Exception
*/
public static function generate(int $id, string $name, string $path)
{
$actions = self::defaultAction();
$delete = [];
$insert = [];
$created = date('Y-m-d H:i:s');
foreach ($actions as $key => $action) {
$name = explode(':', $name)[0];
$delete[] = $name.':'.$key;
$arr = [];
$arr['title'] = $action;
$arr['pid'] = $id;
$arr['name'] = $name.':'.$key;
$arr['type'] = self::TYPE_ACTION;
$arr['path'] = $path.$id.',';
$arr['remark'] = sprintf("自动生成[%s][%s]操作", $name, $action);
$arr['created_at'] = $created;
$insert[] = $arr;
}
//删除已有常规操作
self::where('pid', $id)->whereIn('name', $delete)->delete();
//新增常规操作
(new self())->saveAll($insert);
}
}

18
app/model/Model.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace app\model;
class Model extends Base
{
//获取模型列表
public static function getList()
{
return self::order('sort asc')
->select()
->toArray();
}
public static function onAfterInsert($model)
{
$model->sort = $model->id;
$model->save();
}
}

8
app/model/Role.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace app\model;
class Role extends Base
{
public const STATUS_NORMAL = 1; //正常
public const STATUS_DISABLE = 0;//禁用
}

7
app/model/Rules.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace app\model;
class Rules extends Base
{
}

86
app/model/Sku.php Normal file
View File

@ -0,0 +1,86 @@
<?php
namespace app\model;
use think\Model;
use think\model\relation\HasOne;
class Sku extends Base
{
// SKU 状态
public const STATUS_ON = '1'; // 出售中
public const STATUS_OFF = '0'; // 已下架
// SKU 积分兑换限制
public const ALLOW_ONLY_BUY = '0';
public const ALLOW_BUY_AND_SCORE = '1';
public const ALLOW_ONLY_SCORE = '2';
public static function statusTextList(): array
{
return [
(string)self::STATUS_ON => '在售',
(string)self::STATUS_OFF => '已下架',
];
}
public static function allowScoreTextList(): array
{
return [
(string)self::ALLOW_ONLY_BUY => '购买',
(string)self::ALLOW_BUY_AND_SCORE => '购买或积分兑换',
(string)self::ALLOW_ONLY_SCORE => '积分兑换',
];
}
public static function onAfterInsert(Model $item)
{
$item->sort = $item->id;
$item->save();
}
/**
* 模型关联:商品分类
*
* @return HasOne
*/
public function category(): HasOne
{
return $this->hasOne(SkuCategory::class, 'id', 'category_id');
}
/**
* 生成 SKU coding
* 年份 + 2位随机码 + 时间戳(秒[10] + 微妙[4]
* @length 18
*/
public static function generateCoding(): string
{
$randStr = str_pad(mt_rand(1,99), 2, '0', STR_PAD_LEFT);
$prefix = date('y').$randStr;
$mt = microtime(true);
list($time, $micro) = explode('.', $mt);
$micro = str_pad($micro, 4, '0', STR_PAD_RIGHT);
$micro = substr($micro, 0, 4);
return $prefix.$time.$micro;
}
/**
* @return HasOne
*/
public function spu(): HasOne
{
return $this->hasOne(Spu::class, 'id', 'spu_id');
}
/**
* 关联活动商品
*
* @return HasOne
*/
public function activity(): HasOne
{
return $this->hasOne(SpuActivity::class, 'id', 'spu_activity_id');
}
}

40
app/model/Slide.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace app\model;
class Slide extends Base
{
public static function delByIds($ids)
{
return self::whereIn('id', $ids)->delete();
}
//获取幻灯片列表
public static function getList()
{
return self::order("sort asc")
->select()
->toArray();
}
public static function onAfterInsert($slide)
{
$slide->sort = $slide->id;
$slide->save();
}
//获取轮播图涉及到的文件
public static function getFilesInUse()
{
$items = self::select()->toArray();
$data = [];
foreach ($items as $item) {
$src = trim($item['src']);
if (!empty($src)) {
$key = getKeyByPath($src);
$data[$key] = $src;
}
}
return $data;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\model;
/**
* 轮播图显示位置
*
* Class SlidePosition
* @package app\model
*/
class SlidePosition extends Base
{
const home_position = "home-banner";
public static function allPosition()
{
return self::select();
}
}

7
app/model/SmsLog.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace app\model;
class SmsLog extends Base
{
}

54
app/model/System.php Normal file
View File

@ -0,0 +1,54 @@
<?php
namespace app\model;
class System extends Base
{
//获取友情链接上传图片尺寸
public static function getLinkImageSize()
{
$system = self::getSystem();
if(!empty($system['link_w']) && !empty($system['link_h'])){
$linkSize = $system['link_w'] . '像素 X ' . $system['link_h'] . '像素';
}else{
$linkSize = '';
}
return $linkSize;
}
//获取幻灯片上传尺寸
public static function getSlideImageSize()
{
$system = self::getSystem();
if(!empty($system['slide_w']) && !empty($system['slide_h'])){
$slideSize = $system['slide_w'] . '像素 X ' . $system['slide_h'] . '像素';
}else{
$slideSize = '';
}
if(!empty($system['slide_mobile_w']) && !empty($system['slide_mobile_h'])){
$slideMobileSize = $system['slide_mobile_w'] . '像素 X ' . $system['slide_mobile_h'] . '像素';
}else{
$slideMobileSize = '';
}
return [
'slide_size' => $slideSize,
'slide_mobile_size' => $slideMobileSize
];
}
//获取文章图片尺寸
public static function getArticleImageSize()
{
$system = self::getSystem();
if(!empty($system['article_w']) && !empty($system['article_h'])){
$articleSize = $system['article_w'] . '像素 X ' . $system['article_h'] . '像素';
}else{
$articleSize = '';
}
return $articleSize;
}
//获取系统配置信息
public static function getSystem()
{
return self::order('id asc')
->findOrEmpty()
->toArray();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
<?php
namespace app\repository;
use app\exception\RepositoryException;
use app\model\AccountRecord;
use app\model\Archives;
use app\model\ArchivesCategory;
use app\model\ArchivesModel;
use app\model\Course;
use app\model\Diary;
use app\model\Disease;
use app\model\DoctorRelation;
use Exception;
use think\Model;
use think\Collection;
use app\service\Repository;
use think\db\exception\DbException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
/**
* 内容 相关
*
* Class ArchivesRepository
* @package app\repository
* @method self getInstance(Model $model = null) static
*/
class ArchivesRepository extends Repository
{
}

View File

@ -0,0 +1,48 @@
<?php
namespace app\repository;
use think\Model;
use app\model\Block;
use app\model\Category;
use app\service\Repository;
/**
* 自定义领域 相关
*
* Class BlockRepository
* @package app\repository
* @method self getInstance(Model $model = null) static
*/
class BlockRepository extends Repository
{
public const CATEGORY_DTC = 'dtc'; //dtc认证
public const CATEGORY_NEWS = 'news'; //活动信息
public const CATEGORY_CONTACT = 'contact'; //联系我们
public const CATEGORY_ABOUT = 'about'; //关于我们
/**
* @param int $cateId
* @return array
*/
public function getByCateId(int $cateId): array
{
return Block::getByCategoryId($cateId);
}
/**
* 分类标识
*
* @param string $name 通过栏目分类标识获取Block
* @return array
*/
public function getByCateName(string $name): array
{
$categoryId = Category::where('name', $name)->value('id');
if ($categoryId) {
return Block::getByCategoryId($categoryId);
}
return [];
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace app\repository;
use app\traits\cms\ArticleTrait;
use app\traits\cms\MenuTrait;
use tauthz\facade\Enforcer;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Model;
use app\service\Repository;
/**
* CMS 基础功能仓储
*
* Class CmsRepository
* @package app\repository
* @method self getInstance(Model $model = null) static
*/
class CmsRepository extends Repository
{
use MenuTrait;
use ArticleTrait;
/**
* xmSelect下拉列表格式处理
* [['title' => 'aa', 'value' => 1, 'selected' => true, 'prefix' => '&nbsp;&nbsp;&nbsp;&nbsp;']]
*
* @param array $data 待处理的数据
* @param string $symbol 分隔符号 默认 &nbsp;
* @param int $repeatNum 重复次数 默认4
* @return array
*/
public function handleSelectedList(array $data, string $symbol = '&nbsp;', int $repeatNum = 4): array
{
$list = [];
foreach ($data as $item) {
$level = $item['level'] ?? 0;
$arr = $item;
$arr['children'] = $arr['children'] ?? [];
$arr['prefix'] = str_repeat($symbol, $level * $repeatNum);
$list[] = $arr;
}
return $list;
}
/**
* 获取后台用户权限列表
*
* @param int $accountId
* @return array
*/
public function getUserRules(int $accountId): array
{
$rules = [];
$roles = Enforcer::getRolesForUser($accountId);
foreach ($roles as $role) {
$rules = array_merge($rules, Enforcer::getPermissionsForUser($role));
}
$ruleNameList = [];
foreach ($rules as $rule) {
if (isset($rule[2])) {
$ruleNameList[] = $rule[1].':'.$rule[2];
} else {
$ruleNameList[] = $rule[1];
}
}
return array_unique($ruleNameList);
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace app\repository;
use app\model\SmsLog;
use app\service\Repository;
use app\service\Sms;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Log;
use think\Model;
/**
* 通用域 相关操作
*
* Class CommonRepository
* @package app\repository
* @method self getInstance(Model $model = null) static
*/
class CommonRepository extends Repository
{
public const SMS_TYPE_REGISTER = 'register';//注册
public const SMS_TYPE_LOGIN = 'login';//登录
public const SMS_TYPE_BINDING = 'binding';//绑定
public const SMS_TYPE_EDIT_PASSWORD = 'edit_password';//修改密码
public const SMS_STATUS_OK = 1;//短信验证通过
public const SMS_STATUS_WAITING = 0;//验证码待检测
/**
* 发送短信
*
* @param string $phone
* @param string $type
* @return bool
*/
public function sendSms(string $phone, string $type): bool
{
try {
$now = time();
$res = SmsLog::create([
'phone' => $phone,
'type' => $type,
'code' => rand(100000, 999999),
'created_at' => date('Y-m-d H:i:s', $now),
'expired_at' => date('Y-m-d H:i:s', $now + 5 * 60),
]);
$res = Sms::send($phone, $res['code']);
if (is_bool($res)) {
return $res;
}
Log::error(json_encode($res, JSON_FORCE_OBJECT));
return false;
} catch (Exception $e) {
Log::error(sprintf("[发送短信验证码失败]%s:%d %s", $e->getFile(), $e->getLine(), $e->getMessage()));
return false;
}
}
/**
* 检查短信验证码
*
* @param string $phone
* @param string $code
* @param string $type
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function checkSms(string $phone, string $code, string $type): bool
{
$item = (new SmsLog())->where('phone', $phone)
->where('type', $type)
->where('code', $code)
->order('created_at', 'desc')
->order('id', 'desc')
->find();
if (!$item) {
return false;
}
if ($item['expired_at'] < date('Y-m-d H:i:s')) {
return false;
}
return $item->save(['status' => self::SMS_STATUS_OK]);
}
/**
* 日志记录
*
* @param string $msg
* @param Exception|null $e
* @param string $level
* @param string $channel
*/
public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file')
{
if ($e != null) {
$msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage());
} else {
$msg = sprintf("%s", $msg);
}
Log::channel($channel)->$level($msg);
}
}

View File

@ -0,0 +1,212 @@
<?php
namespace app\repository;
use app\model\SlidePosition;
use Exception;
use think\Model;
use app\model\Slide;
use think\Collection;
use app\service\Repository;
use think\db\exception\DbException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
/**
* 运营领域 相关
*
* Class OperationRepository
* @package app\repository
* @method self getInstance(Model $model = null) static
*/
class OperationRepository extends Repository
{
/**
* 轮播位置
*
* @return string[][]
*/
public function slidePositions(): array
{
try {
return SlidePosition::allPosition()->toArray();
} catch (Exception $e) {
return [];
}
}
/**
* 获取轮播
*
* @param int $id
* @return array|Model|null
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function findSlideById(int $id)
{
return Slide::findById($id);
}
/**
* 轮播列表
*
* @param array $where
* @param array $fields
* @param int $page
* @param int $size
* @param callable|null $call
* @param array $orders
* @return array
* @throws Exception
*/
public function slideList(array $where=[], array $fields=[], int $page=1, int $size=20, callable $call=null, array $orders=[]): array
{
return Slide::findList($where, $fields, $page, $size, $call, $orders);
}
/**
* 更新轮播
*
* @param array $data
* @param int $id
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function updateSlide(array $data, int $id): bool
{
$item = (new Slide())->where('id', $id)->find();
if ($item) {
return $item->save($data);
}
return false;
}
/**
* 创建轮播
*
* @param array $data
* @return Slide
*/
public function createSlide(array $data): Slide
{
$data['created_at'] = date('y-m-d H:i:s');
return Slide::create($data);
}
/**
* 删除轮播图
*
* @param array $ids
* @return bool
*/
public function deleteSlides(array $ids): bool
{
return Slide::deleteByIds($ids);
}
/**
* 轮播位置是否存在
*
* @param string $position
* @param int $exceptId 需要排除的显示位置ID
* @return bool
*/
public function slidePositionExists(string $position, int $exceptId = 0): bool
{
return (new SlidePosition())->when($exceptId > 0, function ($q) use ($exceptId) {
$q->where('id', '<>', $exceptId);
})->where('key', $position)->count() > 0;
}
/**
* 根据显示位置查询相关的轮播图信息
*
* @param string $position 轮播图位置标识
* @param int $size 限制查询数量
* @throws Exception
*/
public function slideListByPosition(string $position, int $size=0): ?Collection
{
$where[] = ['position', '=', $position];
$orders = ['sort'=>'asc'];
return Slide::findList($where, [], 1, $size, null, $orders)['list'];
}
/**
* 轮播显示位置列表
*
* @param array $where
* @param array $fields
* @param int $page
* @param int $size
* @param callable|null $call
* @param array $orders
* @return array
* @throws Exception
*/
public function slidePositionList(array $where=[], array $fields=[], int $page=1, int $size=20, callable $call=null, array $orders=[]): array
{
return SlidePosition::findList($where, $fields, $page, $size, $call, $orders);
}
/**
* 更新轮播位置
*
* @param array $data
* @param int $id
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function updateSlidePosition(array $data, int $id): bool
{
$item = (new SlidePosition())->where('id', $id)->find();
if ($item) {
$item->save($data);
}
return false;
}
/**
* 创建轮播位置
*
* @param array $data
* @return SlidePosition
*/
public function createSlidePosition(array $data): SlidePosition
{
$data['created_at'] = date('y-m-d H:i:s');
return SlidePosition::create($data);
}
/**
* 删除轮播位置
*
* @param array $ids
* @return bool
*/
public function deleteSlidePositions(array $ids): bool
{
return SlidePosition::deleteByIds($ids);
}
/**
* 获取轮播位置
*
* @param int $id
* @return array|Model|null
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function findSlidePositionById(int $id)
{
return SlidePosition::findById($id);
}
}

9
app/service.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use tauthz\TauthzService;
// 系统服务定义文件
// 服务在完成全局初始化之后执行
return [
TauthzService::class,
];

46
app/service/AliOss.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace app\service;
use OSS\Core\OssException;
use OSS\OssClient;
use think\facade\Config;
use think\facade\Log;
class AliOss
{
private static $oss = null;
private function __construct()
{
}
private function __clone()
{
}
/**
* 阿里云OSS
*
*/
public static function config()
{
Config::load('extra/alioss', 'alioss');
return config('alioss');
}
// 阿里云OSS实例 单例模式
public static function instance(): ?OssClient
{
if (self::$oss == null) {
try {
$conf = self::config();
self::$oss = new OssClient($conf['accessKeyId'], $conf['accessKeySecret'], $conf['endpoint']);
} catch (OssException $e) {
Log::error('实例化阿里云OSS失败: ' . $e->getMessage());
return null;
}
}
return self::$oss;
}
}

64
app/service/Alipay.php Normal file
View File

@ -0,0 +1,64 @@
<?php
namespace app\service;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Log;
use think\facade\Config;
class Alipay
{
private static $app = null;
private function __construct()
{
}
private function __clone()
{
}
/**
* 支付宝配置
*
* @return array
*/
public static function config(): array
{
Config::load('extra/alipay', 'alipay');
$conf = config('alipay');
return [
'app_id' => trim($conf['appId']),
'notify_url' => trim($conf['notify_url']),
'return_url' => trim($conf['return_url']) ?? trim($conf['notify_url']),
'ali_public_key' => trim($conf['aliPubKey']),//注意 这里的是支付宝的公钥
// 加密方式: **RSA2**
'private_key' => trim($conf['priKey']),
// 使用公钥证书模式请配置下面两个参数同时修改ali_public_key为以.crt结尾的支付宝公钥证书路径
// 如(./cert/alipayCertPublicKey_RSA2.crt
// 'app_cert_public_key' => './cert/appCertPublicKey.crt', //应用公钥证书路径
// 'alipay_root_cert' => './cert/alipayRootCert.crt', //支付宝根证书路径
'log' => [ // optional
'file' => './logs/alipay.log',
'level' => 'debug', // 建议生产环境等级调整为 info开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
],
// 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
];
}
//支付宝支付实例 单例模式
public static function getInstance(): ?\Yansongda\Pay\Gateways\Alipay
{
if (self::$app == null) {
self::$app = Pay::alipay(self::config());
}
return self::$app;
}
}

204
app/service/DxtcPage.php Normal file
View File

@ -0,0 +1,204 @@
<?php
namespace app\service;
use think\Paginator;
//自定义分页驱动
class DxtcPage extends Paginator
{
protected $linkStr;
/**
* 上一页按钮
* @param string $text
* @return string
*/
protected function getPreviousButton(string $text = "<"): string
{
if ($this->currentPage() <= 1) {
return ('<a href="javascript:;">上一页</a>');
}
$url = $this->url(
$this->currentPage() - 1
);
// return ($this->getPageLinkWrapper($url, "上一页" ,"") .$this->getPageLinkWrapper($url, $text ,"prev"));
return ($this->getPageLinkWrapper($url, "上一页" ,"prev"));
}
/**
* 下一页按钮
* @param string $text
* @return string
*/
protected function getNextButton(string $text = '>'): string
{
if (!$this->hasMore) {
//return ('<div class="li"><a href="javascript:;" class="prev">下一页</a></div>');
return ('<a href="">下一页</a>');
}
$url = $this->url($this->currentPage() + 1);
return ($this->getPageLinkWrapper($url, "下一页",'next')) ;
}
/**
* 页码按钮
* @return string
*/
protected function getLinks(): string
{
if ($this->simple) {
return '';
}
$block = [
'first' => null,
'slider' => null,
'last' => null,
];
$side = 3;
$window = $side * 1;
if ($this->lastPage < $window + 3) {
$block['first'] = $this->getUrlRange(1, $this->lastPage);
} elseif ($this->currentPage <= $window) {
$block['first'] = $this->getUrlRange(1, $window );
$block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
} elseif ($this->currentPage > ($this->lastPage - $window)) {
$block['first'] = $this->getUrlRange(1, 2);
$block['last'] = $this->getUrlRange($this->lastPage - ($window ), $this->lastPage);
} else {
$block['first'] = $this->getUrlRange(1, 2);
$block['slider'] = $this->getUrlRange($this->currentPage , $this->currentPage );
$block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage);
}
$html = '';
if (is_array($block['first'])) {
$html .= $this->getUrlLinks($block['first']);
}
if (is_array($block['slider'])) {
$html .= $this->getDots();
$html .= $this->getUrlLinks($block['slider']);
}
if (is_array($block['last'])) {
$html .= $this->getDots();
$html .= $this->getUrlLinks($block['last']);
}
return $html;
}
/**
* 渲染分页html
* @return mixed
*/
public function render($linkStr='')
{
$this->linkStr=$linkStr;
if ($this->hasPages()) {
if ($this->simple) {
return sprintf(
'<div class=" pase-size">%s %s</div>',
$this->getPreviousButton(),
$this->getNextButton()
);
} else {
return sprintf(
'<div class="pase-size" >%s %s %s</div>',
$this->getPreviousButton(),
$this->getLinks(),
$this->getNextButton()
);
}
}
}
/**
* 生成一个可点击的按钮
*
* @param string $url
* @param string $page
* @return string
*/
protected function getAvailablePageWrapper(string $url, string $page,$class=""): string
{
//return '<li class="li" ><a href="' . htmlentities($url) . $this->linkStr.'" class="'.$class.'">' . $page . '</a></li>';
return '<a href="' . htmlentities($url) . $this->linkStr.'" class="'.$class.'">' . $page . '</a>';
}
/**
* 生成一个禁用的按钮
*
* @param string $text
* @return string
*/
protected function getDisabledTextWrapper(string $text,$class=""): string
{
//return '<li class="li disabled" ><a class="'.$class.'">' . $text . '</a></li> ';
return '<a class="'.$class.' disabled">' . $text . '</a>';
}
/**
* 生成一个激活的按钮
*
* @param string $text
* @return string
*/
protected function getActivePageWrapper(string $text,$class=""): string
{
//return '<li class="li"><a class="'.$class.'">' . $text . '</a></li>';
return '<a class="'.$class.'">' . $text . '</a>';
}
/**
* 生成省略号按钮
*
* @return string
*/
protected function getDots(): string
{
return $this->getDisabledTextWrapper('...');
}
/**
* 批量生成页码按钮.
*
* @param array $urls
* @return string
*/
protected function getUrlLinks(array $urls): string
{
$html = '';
foreach ($urls as $page => $url) {
$html .= $this->getPageLinkWrapper($url, $page);
}
return $html;
}
/**
* 生成普通页码按钮
*
* @param string $url
* @param string $page
* @return string
*/
protected function getPageLinkWrapper(string $url, string $page ,$class=""): string
{
if ($this->currentPage() == $page) {
return $this->getActivePageWrapper($page,'active');
}
return $this->getAvailablePageWrapper($url, $page,$class);
}
}

201
app/service/Excel.php Normal file
View File

@ -0,0 +1,201 @@
<?php
namespace app\service;
use PhpOffice\PhpSpreadsheet\Shared\Date as EDate;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\file\UploadedFile;
class Excel
{
// 导出excel默认样式
public static array $excelStyle = [
'font' => [
'name' => '宋体',
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER, // 水平居中
'vertical' => Alignment::VERTICAL_CENTER, // 垂直居中
'wrapText' => true,
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb' => 'eeeeee'],
]
],
];
public static array $defaultSetting = [
'cell_width' => 30, // 默认列宽
'font_size' => 12, // 默认excel内容字体大小
];
//导出
static public function export($spreadsheet,$filename)
{
$writer = new Xlsx($spreadsheet);
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header('Content-Disposition:inline;filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Pragma: no-cache");
$writer->save('php://output');
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
exit;
}
public static function cancelTimeLimit()
{
ini_set('max_execution_time', '0');
ini_set("memory_limit", '-1');
set_time_limit(0);
}
/**
* 根据header字段获取可配置列的cellId列表
* 默认前26列为可配置列A~Z, $headerLength不能超过26 * 27 = 702
*
* @param int $headerLength
* @return array
*/
public static function getCellIds(int $headerLength): array
{
$defaultCellIds = range('A', 'Z', 1);
$cellIds = $defaultCellIds;
$loop = ceil($headerLength / 26);
if($loop>1) {
$maxPrefixIndex = ($loop - 2) >= 25 ? 25 : ($loop - 2);
for ($prefixIndex = 0; $prefixIndex<= $maxPrefixIndex; $prefixIndex++) {
$prefix = $defaultCellIds[$prefixIndex];
$cellIds = array_merge($cellIds, array_map(function ($val) use($prefix) {
return $prefix.$val;
}, $defaultCellIds));
}
}
return $cellIds;
}
/**
* 设置导出表数据
*
* @param Worksheet $sheet 工作表对象
* @param array $cellValues 数据信息,二维数组(ps若字段值需要指定样式以数组格式传递[$val, DataType::TYPE_STRING])。第二行开始填充
* @param array $header 表头信息, 第一行为表头
* @param string $sheetTitle 工作表标题
* @param array $cellWidthList 列宽样式,键值对,键名为列序号(从0开始计算)
* @param array $excelStyle 表数据样式
* @param array $defaultList 默认设置样式
* @param array $cellWrapList 列换行样式
* @return bool
*/
public static function setExcelCells(Worksheet $sheet, array $cellValues, array $header, string $sheetTitle = '数据列表', $cellWidthList = [], $excelStyle = [], $defaultList = [], $cellWrapList = []): bool
{
$defaultStyle = [
'font' => [
'name' => '宋体',
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER, // 水平居中
'vertical' => Alignment::VERTICAL_CENTER, // 垂直居中
'wrapText' => false,
],
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['rgb'=>'eeeeee'],
]
],
];
$headerLength = count($header);
if($headerLength === 0) return false;
$cellIds = self::getCellIds($headerLength);
$lastCellId = $cellIds[$headerLength-1];
$contentIndex = 2;
foreach ($cellValues as $n => $itemCell) {
foreach ($itemCell as $i => $cellValue) {
if(is_array($cellValue)) {
$sheet->setCellValueExplicit($cellIds[$i] . $contentIndex, ...$cellValue);
} else {
$sheet->setCellValue($cellIds[$i] . $contentIndex, $cellValue);
}
}
$contentIndex++;
}
try {
$headerStr = 'A1:' . $lastCellId.'1';
$bodyStr = 'A2:' . $lastCellId . ($contentIndex - 1);
$defaultWidth = 24; // 默认列宽24个字符
$fontSize = 12; // 默认字体大小为12号
if(!empty($defaultList)) {
if(isset($defaultList['cell_width']) && is_numeric($defaultList['cell_width']) && $defaultList['cell_width'] > 0) {
$defaultWidth = $defaultList['cell_width'];
}
if(isset($defaultList['font_size']) && is_numeric($defaultList['font_size']) && $defaultList['font_size'] > 0) {
$fontSize = $defaultList['font_size'];
}
}
$sheet->setTitle(empty($sheetTitle) ? '数据列表': $sheetTitle);
$sheet->getDefaultColumnDimension()->setAutoSize($fontSize);
$sheet->getStyle($headerStr)->getFont()->setBold(false)->setSize($fontSize+2);
$sheet->getDefaultColumnDimension()->setWidth($defaultWidth);
$sheet->fromArray($header, null, 'A1');
$sheet->getStyle($headerStr)->applyFromArray($defaultStyle);
$sheet->getStyle($bodyStr)->applyFromArray(empty($excelStyle) ? $defaultStyle : $excelStyle);
// 自定义列宽
if(!empty($cellWidthList)) {
foreach ($cellWidthList as $cellId => $widthVal) {
if(isset($cellIds[$cellId]) && is_numeric($widthVal) && $widthVal > 0) {
$sheet->getColumnDimension($cellIds[$cellId])->setWidth($widthVal);
}
}
}
//自定义列是否换行
if(!empty($cellWrapList)) {
foreach ($cellWrapList as $cellId => $boolVal) {
if(isset($cellIds[$cellId])) {
$wrap = $boolVal ? true : false;
$sheet->getStyle($cellIds[$cellId])->getAlignment()->setWrapText($wrap);
}
}
}
return true;
} catch (\Exception $e) {
return false;
}
}
// excel导入时间转换
public static function getExcelTime($timeStr, $format = 'Y-m-d')
{
$timezone = ini_get('date.timezone');
$timeStr = trim($timeStr);
if (!empty($timeStr)) {
if (is_numeric($timeStr)) {
$toTimestamp = EDate::excelToTimestamp($timeStr, $timezone);
$timeStr = date($format, $toTimestamp);
} else {
$toTimestamp = strtotime($timeStr);
$timeStr = ($toTimestamp === false) ? '' : date($format, $toTimestamp);
}
} else {
$timeStr = '';
}
return $timeStr;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace app\service;
use think\facade\Config as CConfig;
/**
* 扩展配置项工具类
* Class ExtraConfig
* @package app\service
*/
class ExtraConfig
{
private static string $extraDir = 'extra/';
/**
* 比率设置
* @return array|mixed
*/
public static function ratio()
{
CConfig::load(self::$extraDir.'ratio', 'ratio');
return config('ratio') ?? [];
}
/**
* 微信相关设置
* @return array|mixed
*/
public static function wechat()
{
CConfig::load(self::$extraDir.'wechat', 'wechat');
return config('wechat') ?? [];
}
/**
* 小程序个性装修配置
* @return array|mixed
*/
public static function miniProgram()
{
CConfig::load('extra/mini_program', 'mini_program');
return config('mini_program') ?? [];
}
/**
* 基础配置
*/
public static function base()
{
CConfig::load('extra/base', 'base');
return config('base') ?? [];
}
/**
* 阿里OSS配置
*/
public static function aliOss()
{
CConfig::load('extra/alioss', 'alioss');
return config('alioss') ?? [];
}
}

65
app/service/File.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace app\service;
use think\file\UploadedFile;
class File
{
//上传文件移动到上传文件夹
public static function move(UploadedFile $file)
{
$upload_path = 'storage/uploads/' . date('Ymd');
$path = app()->getRootPath() . $upload_path;
$filename = uniqid() . '.' . $file->extension();
$upload_filename = '/' . $upload_path . '/' . $filename;
return [$file->move($path, $filename), $file, $upload_filename];
}
/**
* 文件访问路径转换为完整的url
* @param string|null $fileUrl
* @param bool $ossAnalysis 是否进行OSS解析
* @return string
* @todo 若启用OOS存储需根据业务配置调整$fileDomain
*
*/
public static function convertCompleteFileUrl(?string $fileUrl, bool $ossAnalysis=true): string
{
if (empty($fileUrl)) {
return '';
}
if ($ossAnalysis) {
$fileDomain = self::getFileDomain();
} else {
$fileDomain = request()->domain();
}
$prefix = substr($fileUrl, 0, 4);
if (!($prefix == 'http')) {
$fileUrl = $fileDomain.'/'.ltrim($fileUrl, '/');
}
return $fileUrl;
}
/**
* 文件访问域名前缀
*
* @return string
*/
public static function getFileDomain(): string
{
$confBase = ExtraConfig::base();
$confOss = ExtraConfig::aliOss();
$isOss = $confBase['oss'] ?? 'false';
$ossDomain = $confOss['customDomain'] ?? '';
// 默认为当前域名
$fileDomain = request()->domain();
if ($isOss == 'true' && !empty($ossDomain)) {
$fileDomain = $ossDomain;
}
$fileDomain = trim($fileDomain);
return rtrim($fileDomain, '/');
}
}

140
app/service/GdTool.php Normal file
View File

@ -0,0 +1,140 @@
<?php
namespace app\service;
use think\Exception;
/**
* GD2库绘画
* Class GdTool
* @package app\service
*/
class GdTool
{
/**
* 生成海报
* 背景尺寸固定: 750 * 1334
*
* @param string $srcQr 生成的二维码base64值
* @param string $bgImg 背景图片 自定义背景图
* @param string $savePath 海报保存路径为空则返回base64值
* @return bool|string
* @throws \Exception
*/
public static function generatePoster(string $srcQr='', string $bgImg = '', string $savePath = '')
{
try {
if (!empty($savePath)) {
if (!is_dir($savePath)) {
@mkdir($savePath, 0777, true);
}
if (!is_dir($savePath) || !is_writeable($savePath)) {
throw new \Exception('无法保存');
}
}
//1、创建画布资源
// 背景尺寸: 750 * 1334
$defBga = app()->getRootPath().'public/static/images/poster-bg1.png';
$srcBga = empty($bgImg) ? $defBga : $bgImg;
$bgInfo = @getimagesize($srcBga);
if ($bgInfo[0] != 750 || $bgInfo[1] != 1334) {
throw new \Exception('海报模板尺寸不正确!');
}
if (!$bgInfo) {
throw new \Exception('海报背景图资源不存在!');
}
$ext = $bgInfo['mime'];
$extName = '';
$img = null;
switch ($ext) {
case 'image/jpeg':
$img = @imagecreatefromjpeg($srcBga);
$extName = 'jpg';
break;
case 'image/png':
$img = @imagecreatefrompng($srcBga);
$extName = 'png';
break;
}
if (!$img) {
throw new \Exception('无效背景图');
}
//2、准备颜色
$black = imagecolorallocate($img,0,0,0);
$while = imagecolorallocate($img,255,255,255);
$faColor = imagecolorallocate($img,0,104,51);
// 邀请人头像
// $headimgurl = 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJXE3Zz0U5edXYI2icicYibSNwwezWe0X92fovRtpUwdCF5lAmjsYK5EWT3R8ItO0BEqynElYhWibRqDg/132';
// $headimg = imagecreatefromstring(file_get_contents($headimgurl));
// imagecopymerge($img, $headimg, 50, 1000, 0, 0, 120, 120, 100);
// imagecopyresampled($img, $headimg, 90, 900, 0, 0, 120, 120, 120, 120);
// 添加文字
// imagettftext($img, 18, 0, 220, 1100, 250, public_path().'static/simheittf.ttf', '超级凉面...邀您关注');
//填充画布(背景色)
imagefill($img,0,0, $while);
// 组合二维码图片 坐标x:246; y:959
$prefixPng = 'data:image/png;base64,';
$qrStr = base64_decode(str_replace($prefixPng,"",$srcQr));
$qrImg = @imagecreatefromstring($qrStr);
list($qrWidth, $qrHeight) = getimagesize($srcQr);
if(!$qrImg) {
imagedestroy($img);
throw new \Exception('无效二维码');
}
$imgQrW = $imgQrH = 274;
imagecopyresampled($img, $qrImg, 456, 959, 0, 0, $imgQrW, $imgQrH, $qrWidth, $qrHeight);
imagedestroy($qrImg);
//4、输出与保存最终图像(保存文件或返回base64)
if (empty($savePath)) {
/* 返回base64 */
ob_start();
if ($ext == 'image/jpeg') {
imagejpeg($img);
} else {
imagepng($img);
}
$imgData = ob_get_contents();
ob_end_clean();
imagedestroy($img);
$prefix = 'data:image/jpg/png/gif;base64,';
return $prefix.base64_encode($imgData);
} else {
/* 保存到文件*/
$fileName = md5(microtime(true)).'.'.$extName;
$saveFile = $savePath."/".$fileName;
if ($ext == 'image/jpeg') {
imagejpeg($img, $saveFile);
} else {
imagepng($img, $saveFile);
}
}
/*
* 输出显示
header("content-type: image/png");
imagepng($img);
*/
//5、释放画布资源
imagedestroy($img);
} catch (\Exception $e) {
throw $e;
}
return true;
}
}

130
app/service/Image.php Normal file
View File

@ -0,0 +1,130 @@
<?php
namespace app\service;
use think\Image as TImage;
use app\model\System;
class Image extends File
{
/**
* 对图片进行重置大小并对宽度大于max宽度的等比缩放为宽度为1920
* milo
* 2019-10-24修改
*/
public static function resize($src)
{
$max = 1920;
$realPath = app()->getRootPath() . 'public/' . ltrim($src,'/');
if(is_file($realPath)){
$img = TImage::open($realPath);
list($img_w,$img_h) = $img->size();
if($max > 0 && $img_w > $max){
$img->thumb($max, $max * ($img_h / $img_w))->save($realPath);
}
}
}
/**
* 添加水印
* milo
* 2018-01-17
*/
public static function mark($src)
{
$rootPath = app()->getRootPath();
if(!empty($src)){
$system = System::getSystem();
$realPath = $rootPath . 'public/' . ltrim($src, '/');
if(is_file($realPath)){
if($system['is_mark']){
$mark = $rootPath . ltrim($system['mark_img'], '/');
if(is_file($mark)){
$mark_position = $system['mark_position']??5;
$mark_opacity = $system['mark_opacity']??50;
$img = TImage::Open($realPath);
$img->water($mark,$mark_position,$mark_opacity)->save($realPath);
}
}
}
}
}
//获取水印位置键值对
public static function getMarkPosition()
{
return [
"1" => '上左',
"2" => '上中',
"3" => '上右',
"4" => '中左',
"5" => '正中',
"6" => '中右',
"7" => '下左',
"8" => '下中',
"9" => '下右'
];
}
/**
* 删除图片
* milo
* 2018-01-15
*/
public static function delImg($src)
{
if(!empty(trim($src))){
$realPath = app()->getRootPath() . 'public/' . ltrim($src, '/');
if (file_exists($realPath)) {
$info = pathinfo($realPath);
$source = $info['dirname'] . "/" . $info['filename'] . '*.' . $info['extension'];
foreach(glob($source) as $filename){
if(is_file($filename)){
unlink($filename);
}
}
clearstatcache();// 清除缓存
}
}
}
/**
* 获取缩略图
* milo
* 2019-10-24修改
* 避免跨平台出错,目录分隔符全部转换为'/'
* app()->getRuntimePath() = app()->getRootPath().'runtime/当前应用模块api/'
*/
public static function getThumb($src,$width=0,$height=0,$type = TImage::THUMB_CENTER)
{
if(empty($src)){
return '';
}
$rootPath = app()->getRootPath();
$realPath = $rootPath . 'public/' . ltrim($src, '/');
$realPath = str_replace('\\', '/', $realPath);
if(!file_exists($realPath)){
return '';
}
$info = pathinfo($src);
if($width <= 0 && $height <= 0){ //高宽都小于或等于0则返回原图片
return $src;
}
$image = TImage::open($realPath);
list($imageWidth, $imageHeight) = $image->size();
if($width <= 0){
$width = floor($height * ($imageWidth / $imageHeight));
}elseif($height <= 0){
$height = floor($width * ($imageHeight / $imageWidth));
}
if($width >= $imageWidth || $height >= $imageHeight){
return $src;
}
$thumbName = $info['dirname']. "/" .$info['filename'].'_'.$width.'_'.$height.'.'.$info['extension'];
$realThumbName = $rootPath . 'public/' . ltrim($thumbName, '/');
$realThumbName = str_replace('\\', '/', $realThumbName);
if(!file_exists($realThumbName)){
$image->thumb($width, $height, $type)->save($realThumbName);
}
return str_replace('\\', '/', $thumbName);
}
}

133
app/service/Jwt.php Normal file
View File

@ -0,0 +1,133 @@
<?php
namespace app\service;
use DateTimeImmutable;
use DateTimeZone;
use Exception;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
class Jwt
{
private static $secret = 'lF9XkOMfpsR0ODVfbasY2HtDrIps8GIX';
private static $expire = 7200 * 10;//秒
private static $iss = 'dxtc';//jwt签发者
private static $sub = 'dxtc-customer';//jwt所面向的用户
private static $aud = 'dxtc-customer';//接受jwt的一方
private static function config(): Configuration
{
return Configuration::forSymmetricSigner(
// You may use any HMAC variations (256, 384, and 512)
new Sha256(),
// replace the value below with a key of your own!
InMemory::base64Encoded(self::$secret)
// You may also override the JOSE encoder/decoder if needed by providing extra arguments here
);
}
/**
* 获取token有效期 单位秒
*
* @return int
*/
public static function expire(): int
{
return self::$expire;
}
/**
* 编码
*
* @param $data
* @param int $expire
* @return string
* @throws Exception
*/
public static function generate($data, int $expire = 0): string
{
$expire = $expire <= 0 ? self::$expire : $expire;
$now = new DateTimeImmutable('now', new DateTimeZone('Asia/ShangHai'));
$token = self::config()->builder()
// Configures the issuer (iss claim)
->issuedBy(self::$iss)
// Configures the audience (aud claim)
->permittedFor(self::$aud)
// Configures the id (jti claim)
// ->identifiedBy($this->jti)
// Configures the time that the token was issue (iat claim)
->issuedAt($now)
// Configures the expiration time of the token (exp claim)
->expiresAt($now->modify(sprintf('+%d seconds', $expire)))
// Configures a new claim, called "uid"
->withClaim('data', $data)
// Configures a new header, called "foo"
// ->withHeader('foo', 'bar')
// Builds a new token
->getToken(self::config()->signer(), self::config()->signingKey());
return $token->toString();
}
/**
* 解析
*
* @param string $tokenStr
* @return array|mixed
*/
public static function parse(string $tokenStr)
{
$config = self::config();
try {
$token = $config->parser()->parse($tokenStr);
assert($token instanceof UnencryptedToken);
return $token->claims()->all()['data'] ?? [];
} catch (Exception $e) {
return [];
}
}
/**
* 验证token
*
* @param string $tokenStr
* @return bool
*/
public static function validate(string $tokenStr): bool
{
$config = self::config();
try {
$token = $config->parser()->parse($tokenStr);
assert($token instanceof UnencryptedToken);
//验证签发人iss是否正确
$validateIssued = new IssuedBy(self::$iss);
$config->setValidationConstraints($validateIssued);
//验证客户端aud是否匹配
$validateAud = new PermittedFor(self::$aud);
$config->setValidationConstraints($validateAud);
//验证是否过期 exp
$timezone = new DateTimeZone('Asia/Shanghai');
$now = new SystemClock($timezone);
$validateExpired = new LooseValidAt($now);
$config->setValidationConstraints($validateExpired);
$constraints = $config->validationConstraints();
return $config->validator()->validate($token, ...$constraints);
} catch (Exception $e) {
return false;
}
}
}

79
app/service/Kd100.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace app\service;
class Kd100
{
private static $url = 'http://poll.kuaidi100.com/poll/query.do'; //实时查询请求地址
private static $key = 'bRIlFitN9883';//客户授权key
private static $customer = '03A67821933221AAE6CBACBCA7E565F2';//查询公司编号
public static function query(string $com, string $num, $returnArray = false)
{
$param = [
'com' => $com, //快递公司编码, 一律用小写字母
'num' => $num, //快递单号
// 'phone' => '', //手机号
// 'from' => '', //出发地城市
// 'to' => '', //目的地城市
// 'resultv2' => '1' //开启行政区域解析
];
//请求参数
$post_data = [];
$post_data["customer"] = self::$customer;
$post_data["param"] = json_encode($param);
$sign = md5($post_data["param"].self::$key.$post_data["customer"]);
$post_data["sign"] = strtoupper($sign);
$params = "";
foreach ($post_data as $k => $v) {
$params .= "$k=".urlencode($v)."&"; //默认UTF-8编码格式
}
$post_data = substr($params, 0, -1);
//发送post请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_URL, self::$url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
return json_decode($result, $returnArray);
}
/**
* 状态
*
* @return string[]
*/
public static function state(): array
{
return [
0 => '在途',
1 => '揽收',
2 => '疑难',
3 => '签收',
4 => '退签',
5 => '派件',
6 => '退回',
7 => '转单',
10 => '待清关',
11 => '清关中',
12 => '已清关',
13 => '清关异常',
14 => '收件人拒签',
];
}
/**
* 不在继续查询的配送状态
* @return int[]
*/
public static function unSearchState(): array
{
return [3,4,12];
}
}

60
app/service/Math.php Normal file
View File

@ -0,0 +1,60 @@
<?php
namespace app\service;
/**
* 精准计算 依赖bc库
*
* Class Math
* @package app\service
*/
class Math
{
// 加法
public static function add(string $num1, string $num2, $scale = 2): string
{
return bcadd($num1, $num2, $scale);
}
// 减法
public static function sub(string $num1, string $num2, $scale = 2): string
{
return bcsub($num1, $num2, $scale);
}
// 乘法
public static function mul(string $num1, string $num2, $scale = 2): string
{
return bcmul($num1, $num2, $scale);
}
// 除法
public static function div(string $num1, string $num2, $scale = 2): string
{
return bcdiv($num1, $num2, $scale);
}
// 求余、取模
public static function mod(string $num1, string $num2, $scale = 2): string
{
return bcmod($num1, $num2, $scale);
}
// 分转元
public static function fen2Yuan(int $amount): string
{
return self::div($amount, '100');
}
// 元转分
public static function yuan2Fen(string $amount, int $scale = 0): string
{
return self::mul($amount, '100', $scale);
}
// 格式化 默认保留小数点后两位
public static function format($amount, int $scale = 2): string
{
return number_format($amount, $scale, '.', '');
}
}

306
app/service/Repository.php Normal file
View File

@ -0,0 +1,306 @@
<?php
namespace app\service;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\Model;
use think\facade\Db;
use think\Collection;
use think\facade\Env;
use think\facade\Log;
use think\Paginator;
use app\exception\RepositoryException;
/**
* 领域模型-仓储
*
* Class Repository
* @package app\service
*/
class Repository
{
/**
* @var array 已加载对象列表
*/
private static $objects = [];
/**
* @var Model 模型对象
*/
protected $model;
// 布尔值数字关系
public const BOOL_FALSE = 0;
public const BOOL_TRUE = 1;
/**
* 获取当前子对象实列(单例形式返回)
*
* @param Model|null $model 模型对象。未指定则自动获取
* @return mixed
*/
public static function getInstance(Model $model = null)
{
$class = get_called_class();
if (isset(self::$objects[$class]) && self::$objects[$class] !== null) {
return self::$objects[$class];
}
$obj = new $class();
if ($model) {
$obj->model = $model;
} elseif (strpos($class, '\\repository') > 0) {
$model = str_replace('\\repository', '\\model', $class);
//去掉末尾Repository app\model\AccountRepository =》 app\model\Account
$model = substr($model, 0, strlen($model) - strlen('Repository'));
if (class_exists($model)) {
$obj->model = new $model;
}
}
self::$objects[$class] = $obj;
return $obj;
}
/**
* @param callable $callback
* @param mixed $failReturn
* @param bool $transaction
* @return mixed
* @throws RepositoryException
*/
protected function access(callable $callback, $failReturn, bool $transaction = false)
{
$exception = null;
try {
if ($transaction) {
Db::startTrans();
}
$r = $callback();
if ($transaction) {
Db::commit();
}
if ($r) {
return $r;
}
if ($failReturn instanceof Exception) {
return null;
}
return $failReturn;
} catch (Exception $e) {
if ($transaction) {
Db::rollback();
}
if ($e instanceof RepositoryException) {
throw $e;
}
$name = 'Domain - Repository - 未知错误';
$traces = $e->getTrace();
foreach ($traces as $i => $trace) {
if (!empty($trace['class']) && $trace['class'] === Repository::class && $trace['function'] === 'access') {
$trace = $traces[$i - 2] ?? null;
break;
}
}
if (!empty($trace) && !empty($trace['file'][1]) && !empty($trace['line'])) {
$parts = explode('application\\', $trace['file']);
if (!empty($parts[1])) {
$name = $parts[1].':'.$trace['line'];
} else {
$name = $trace['file'].':'.$trace['line'];
}
}
$exception = $e;
$line = '['.$name.'] '.$e->getMessage();
Log::error($line);
throw new RepositoryException('Repository异常'.(Env::get('app_debug') ? ': '.$line : ''), 5009);
} finally {
if ($exception && $failReturn instanceof RepositoryException) {
if (Env::get('app_debug')) {
$failReturn = new RepositoryException($failReturn->getMessage().': '.$exception->getMessage(),
$failReturn->getCode());
}
throw $failReturn;
}
}
}
/**
* 获取当前model对象
*
* @return Model
*/
public function getModel(): Model
{
return $this->model;
}
/**
* 根据条件查询列表
*
* @param array $where 查询条件
* @param array $fields 查询字段 []表示全部
* @param int $page 默认第一页 0不限制
* @param int $limit 限制条数 0不限制
* @param callable|null $callback 更为复杂的条件 使用闭包查询
* @return array
* @throws RepositoryException
*/
public function findList(array $where = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $order = []): ?array
{
$failData = [
'total' => 0,
'current' => $page,
'size' => $limit,
'list' => new Collection(),
];
return $this->access(function () use ($where, $fields, $page, $limit, $callback, $order) {
return $this->model->findList($where, $fields, $page, $limit, $callback, $order);
}, $failData);
}
/**
* 根据条件查询列表[带分页 适用于后台]
*
* @param array $data 查询数据
* @param array $pageParams 分页参数
* @param callable|null $callback 复杂查询条件 使用闭包查询
* @return Paginator
*/
public function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator
{
return $this->model->findListWithPaginate($data, $pageParams, $callback);
}
/**
* 根据主键 ID 查询
*
* @param int $id ID
* @param array $fields 要返回的字段,默认全部
* @return Mixed
* @throws RepositoryException
*/
public function findById(int $id, array $fields = [], callable $callback = null)
{
return $this->access(function () use ($id, $fields, $callback) {
return $this->model->findById($id, $fields, $callback);
}, null);
}
/**
* @param array $where
* @param array $fields
* @return array|Model|null
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function findOneByWhere(array $where, array $fields = [])
{
return $this->model->field($fields)->where($where)->find();
}
/**
* 创建
*
* @param array $data 数据
* @return Model
* @throws RepositoryException
*/
public function create(array $data): Model
{
return $this->access(function () use ($data) {
return $this->model->create($data);
}, new RepositoryException('创建失败'));
}
/**
* 更新
*
* @param array $data 数据
* @param array $where 条件
* @return bool|Exception
* @throws RepositoryException
*/
public function update(array $data, array $where): bool
{
return $this->access(function () use ($data, $where) {
return $this->model->where($where)->find()->save($data);
}, new RepositoryException('更新失败'));
}
/**
* 删除
*
* @param array $where 删除条件
* @param bool $softDelete 是否软删除 默认false
* @param string $softDeleteTime 删除时间 softDelete=true时有效
* @param string $softDeleteField 软删除字段 softDelete=true时有效
* @return bool
* @throws RepositoryException
*/
public function delete(array $where, bool $softDelete = false, string $softDeleteTime = '', string $softDeleteField = 'deleted_at'): bool
{
return $this->access(function () use ($where, $softDelete, $softDeleteField, $softDeleteTime) {
// 注意如果model中引入了软删除trait$softDelete又设置false 将无法正确删除
return $this->model->where($where)
->when($softDelete, function ($q) use ($softDeleteField, $softDeleteTime) {
$softDeleteTime = $softDeleteTime ?: date('Y-m-d H:i:s');
$q->useSoftDelete($softDeleteField, $softDeleteTime);
})->delete();
}, false);
}
/**
* 排序
*
* @param int $id 排序ID
* @param string $type 排序类型 向上、向下
* @param int $num 移动位数
* @param string $listType 列表的排序类型 降序|升序
* @param array $where 额外条件 格式如:
* $map[] = ['name','like','think'];
* $map[] = ['status','=',1];
* @return array
*/
public function sort(int $id,string $type,int $num,string $listType, array $where = []): array
{
return $this->model->sort($id, $type, $num, $listType, $where);
}
/**
* 日志记录
*
* @param string $msg
* @param Exception|null $e
* @param string $level
* @param string $channel
*/
public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file')
{
if ($e != null) {
$msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage());
} else {
$msg = sprintf("%s", $msg);
}
Log::channel($channel)->$level($msg);
}
}

Some files were not shown because too many files have changed in this diff Show More