Merge branch 'develop'

This commit is contained in:
mzl
2025-07-11 18:01:23 +08:00
parent 642c25503d
commit 925c5501f7
83 changed files with 4869 additions and 1162 deletions
@@ -0,0 +1,11 @@
<?php
namespace app\ticket\controller;
use app\ticket\service\TicketService;
use support\Request;
class AttachmentController extends BaseController
{
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace app\ticket\controller;
use support\Request;
use support\Response;
use support\exception\BusinessException;
use app\common\traits\ResponseTrait;
use think\Validate;
abstract class BaseController
{
use ResponseTrait;
protected mixed $validator = null;
/**
* 验证数据
*
* @param Validate $validator 验证器实例
* @param array $data 待验证的数据
* @param string $scene 验证场景
* @return array 验证后的数据
* @throws BusinessException 验证失败时抛出业务异常
*/
public function validate(Validate $validator, array $data, string $scene = ''): array
{
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
if ($scene && method_exists($validator, 'scene')) {
$validator->scene($scene);
}
// 执行数据验证
if (!$validator->check($data)) {
// 验证失败,抛出业务异常,异常信息为验证器返回的错误信息
throw new BusinessException($validator->getError());
}
// 验证成功,返回原始数据
return $data;
}
public function page($result){
$current = $result->currentPage();
$total = $result->total();
$size = $result->perPage();
$result = $result->toArray();
$result['current'] = $current;
$result['total'] = $total;
$result['size'] = $size;
return $result;
}
}
@@ -0,0 +1,64 @@
<?php
namespace app\ticket\controller;
use app\ticket\service\CategorieService;
use support\Request;
class CategorieController extends BaseController
{
public function index(Request $request)
{
try {
$result = (new CategorieService())->list($request->all());
return $this->success($this->page($result));
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function getAll(Request $request)
{
try {
$result = (new CategorieService())->getAll($request->all());
return $this->success($result);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function add(Request $request)
{
try {
return (new CategorieService())->add($request->all())
? $this->success()
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile());
}
}
public function edit(Request $request)
{
try {
$result = (new CategorieService())->edit($request->all());
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile());
}
}
public function delete(Request $request)
{
try {
$result = (new CategorieService())->delete($request->input('id'));
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile());
}
}
}
@@ -0,0 +1,46 @@
<?php
namespace app\ticket\controller;
use app\ticket\service\ConversationService;
use support\Request;
class ConversationController extends BaseController
{
public function index(Request $request)
{
try {
$ticket_id = $request->input('ticket_id');
if(empty($ticket_id)){
return $this->error('ticket_id不能为空');
}
$result = (new ConversationService())->list($request->all());
return $this->success($this->page($result));
} catch (\Throwable $e) {
return $this->error($e->getMessage().$e->getLine().$e->getFile());
}
}
public function add(Request $request)
{
try {
$request->setPost('user_id', $request->user->uid);
return (new ConversationService())->add($request->all())
? $this->success()
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile());
}
}
public function edit(Request $request)
{
try {
$request->setPost('user_id', $request->user->uid);
$result = (new ConversationService())->edit($request->all());
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile());
}
}
}
@@ -0,0 +1,87 @@
<?php
namespace app\ticket\controller;
use app\ticket\service\TicketService;
use support\Request;
class TicketController extends BaseController
{
public function index(Request $request)
{
try {
$result = (new TicketService())->list($request->all());
return $this->success($this->page($result));
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 我的工单
* @param Request $request
* @return \support\Response
*/
public function self(Request $request)
{
try {
$request->setGet('user_id',$request->user->uid);
$result = (new TicketService())->list($request->all());
return $this->success($result);
// return $this->success($this->page($result));
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function add(Request $request)
{
try {
$request->setPost('user_id', $request->user->uid);
return (new TicketService())->add($request->all())
? $this->success()
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
public function edit(Request $request)
{
try {
$request->setPost('user_id', $request->user->uid);
$result = (new TicketService())->edit($request->all());
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
public function status(Request $request)
{
try {
$request->setPost('user_id', $request->user->uid);
$result = (new TicketService())->status($request->all());
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
public function getStatus(Request $request)
{
try {
$result = (new TicketService())->getStatus($request->all());
return $result
? $this->success($result)
: $this->error();
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace app\ticket\logic;
use app\common\model\UserTokenLog;
use app\common\model\Systems;
use app\common\model\User;
use app\common\model\UserSn;
use app\common\utils\Jwt;
use app\ticket\model\Attachment;
use app\ticket\model\Ticket;
use support\Redis;
use support\exception\BusinessException;
use support\Log;
class AttachmentLogic
{
public function adds(array $data,int $ticket_id,int $user_id)
{
try {
foreach ($data as $k=>$v){
$v['ticket_id'] = $ticket_id;
$v['user_id'] = $user_id;
$res = Attachment::create($v);
}
return $res;
} catch (\Throwable $e) {
Log::error('工单创建失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('工单创建失败,请稍后重试', 500);
}
}
public function edit(array $data,int $ticket_id,int $user_id)
{
try {
$attachmentsId = [];
foreach ($data as $k=>$v){
$v['ticket_id'] = $ticket_id;
$v['user_id'] = $user_id;
if(!empty($v['id'])){ //修改附件
$attachmentId = $v['id'];
$attachmentsId[] = $attachmentId;
// $v_res = Attachment::update($attachmentId, $v);
$v_res = Attachment::updateOrCreate(['id' => $v['id']],$v);
if (!$v_res) {
throw new BusinessException('附件保存失败');
}
} else { //新增附件
unset($v['id']);
$v_res = Attachment::create($v);
if (!$v_res) {
throw new BusinessException('附件创建失败');
}
$vId = $v_res->id;
$attachmentsId[] = $vId;
}
}
//删除本次没有出现的附件
$res = Attachment::whereNotIn('id', $attachmentsId)->where('ticket_id',$ticket_id)->delete();
return $res;
} catch (\Throwable $e) {
Log::error('工单编辑(附件)失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('工单编辑失败,请稍后重试', 500);
}
}
public static function generateSimpleTicketNo(): string
{
$date = date('Ymd'); // 日期
$prefix = 'GD'; // 前缀
$random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字
return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace app\ticket\logic;
use app\ticket\model\Categorie;
use support\Log;
use support\exception\BusinessException;
class CategorieLogic
{
public function list($params)
{
$where = [];
$size = $params['size'] ?? 10;
$current = $params['current'] ?? 1;
return Categorie::with('translation')->where($where)->paginate($size, ['*'], 'page', $current);
}
public function getAll($params)
{
$where = [];
$where[] = ['status', '=', 1];
return Categorie::with('translation')->where($where)->get();
}
public function add(array $data)
{
try {
return Categorie::create($data);
} catch (\Throwable $e) {
Log::error('分类创建失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('分类创建失败,请稍后重试', 500);
}
}
public function edit(array $data)
{
try {
$categorie = Categorie::updateOrCreate(['id' => $data['id']], $data);
return $categorie;
} catch (\Throwable $e) {
Log::error('分类编辑失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('分类编辑失败,请稍后重试', 500);
}
}
public function delete(int $id)
{
try {
$categorie = Categorie::where(['id'=>$id])->delete();
return $categorie;
} catch (\Throwable $e) {
Log::error('分类删除失败', [
'id' => $id,
'error' => $e->getMessage()
]);
throw new BusinessException('分类删除失败,请稍后重试', 500);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace app\ticket\logic;
use app\ticket\model\Conversation;
use support\Log;
use support\exception\BusinessException;
class ConversationLogic
{
public function list($params)
{
$where = [];
if (!empty($params['ticket_id'])) {
$where['ticket_id'] = $params['ticket_id'];
}
$size = $params['size'] ?? 10;
$current = $params['current'] ?? 1;
return Conversation::where($where)->paginate($size, ['*'], 'page', $current);
}
public function add(array $data)
{
try {
return Conversation::create($data);
} catch (\Throwable $e) {
Log::error('会话创建失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('会话创建失败,请稍后重试', 500);
}
}
public function edit(array $data)
{
try {
$conversation = Conversation::updateOrCreate(['id' => $data['id']], $data);
return $conversation;
} catch (\Throwable $e) {
Log::error('会话编辑失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('会话编辑失败,请稍后重试', 500);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace app\ticket\logic;
use app\common\model\UserTokenLog;
use app\common\model\Systems;
use app\common\model\User;
use app\common\model\UserSn;
use app\common\utils\Jwt;
use app\ticket\model\Ticket;
use support\Redis;
use support\exception\BusinessException;
use support\Log;
class TicketLogic
{
public function list($params)
{
$where = [];
if(!empty($params['categorie_id'])){
$where[] = ['categorie_id', '=', $params['categorie_id']];
}
if(!empty($params['user_id'])){
$where[] = ['user_id', '=', $params['user_id']];
}
if(!empty($params['status'])){
$where[] = ['status', '=', $params['status']];
}
$size = $params['size'];
$current = $params['current'];
return Ticket::with('attachments')->with('categorie')->with('categorie.translation')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current);
}
public function add(array $data)
{
$data['status'] = 1;
$data['code'] = self::generateSimpleTicketNo();
try {
return Ticket::create($data);
} catch (\Throwable $e) {
Log::error('工单创建失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('工单创建失败,请稍后重试', 500);
}
}
public function edit(array $data)
{
try {
unset($data['code']);
$ticket = Ticket::updateOrCreate(['id' => $data['id']],$data);
return $ticket;
} catch (\Throwable $e) {
Log::error('工单编辑失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('工单编辑失败,请稍后重试', 500);
}
}
public function find(int $id)
{
$ticket = Ticket::find($id);
return $ticket;
}
public static function generateSimpleTicketNo(): string
{
$date = date('Ymd'); // 日期
$prefix = 'GD'; // 前缀
$random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字
return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace app\ticket\model;
use app\common\model\Resource;
use app\common\model\Role;
use app\common\model\Systems;
use support\Model;
/**
* users 用户表
* @property integer $id 用户ID(主键)
* @property string $ticket_id 工单编号
* @property string $user_id 分类id
* @property string $original_name 内容
* @property string $storage_path 排序
* @property string $mime_type 状态
* @property integer $file_size 创建人id
* @property string $created_at
* @property string $updated_at
*/
class Attachment extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tkt_ticket_attachment';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
protected $fillable = [
'ticket_id',
'user_id',
'original_name',
'storage_path',
'mime_type',
'file_size'
];
public function ticket(){
return $this->belongsTo(Ticket::class, 'ticket_id', 'id');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace app\ticket\model;
use Illuminate\Database\Eloquent\SoftDeletes;
use support\Model;
/**
* ticket_categorie 工单分类表
* @property integer $id 主键
* @property string $name 分类名称
* @property string $description 分类描述
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
* @property string $deleted_at 删除时间
*/
class Categorie extends Model
{
use SoftDeletes;
protected $connection = 'mysql';
protected $table = 'tkt_ticket_categorie';
protected $primaryKey = 'id';
public $timestamps = true;
protected $dateFormat = 'U';
protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'updated_at' => 'datetime:Y-m-d H:i:s',
'created_at' => 'datetime:Y-m-d H:i:s',
];
protected $fillable = [
'name',
'description',
];
/**
* 单条翻译
*/
public function translation()
{
return $this->hasOne(CategorieTranslation::class, 'categorie_id')->where('locale', locale());
}
/**
* 多条翻译
*/
public function translations()
{
return $this->hasMany(CategorieTranslation::class, 'categorie_id');
}
public function ticket(){
return $this->hasOne(Ticket::class, 'categorie_id', 'id');
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace app\ticket\model;
use support\Model;
/**
* ticket_categorie_translation 工单分类多语言表
* @property integer $id 主键
* @property integer $categorie_id 分类ID
* @property string $locale 语言
* @property string $name 分类名称
* @property string $description 分类描述
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
*/
class CategorieTranslation extends Model
{
protected $connection = 'mysql';
protected $table = 'tkt_ticket_categorie_translation';
protected $primaryKey = 'id';
public $timestamps = true;
protected $dateFormat = 'U';
protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'updated_at' => 'datetime:Y-m-d H:i:s',
'created_at' => 'datetime:Y-m-d H:i:s',
];
protected $fillable = [
'categorie_id',
'locale',
'name',
'description',
];
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace app\ticket\model;
use support\Model;
/**
* ticket_conversation 工单会话表
* @property integer $id 主键
* @property integer $ticket_id 工单ID
* @property integer $user_id 用户ID
* @property string $content 会话内容
* @property string $created_at 创建时间
* @property string $updated_at 更新时间
*/
class Conversation extends Model
{
protected $connection = 'mysql';
protected $table = 'tkt_ticket_conversation';
protected $primaryKey = 'id';
public $timestamps = true;
protected $dateFormat = 'U';
protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'updated_at' => 'datetime:Y-m-d H:i:s',
'created_at' => 'datetime:Y-m-d H:i:s',
];
protected $fillable = [
'ticket_id',
'user_id',
'content',
];
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace app\ticket\model;
use app\common\model\Resource;
use app\common\model\Role;
use app\common\model\Systems;
use support\Model;
/**
* users 用户表
* @property integer $id 用户ID(主键)
* @property string $code 工单编号
* @property string $categorie_id 分类id
* @property string $content 内容
* @property string $sort 排序
* @property integer $status 状态
* @property string $user_id 创建人id
* @property string $created_at
* @property string $updated_at
*/
class Ticket extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tkt_ticket';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
protected $dates = ['created_at', 'updated_at'];
protected $casts = [
'updated_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式
'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式
];
protected $fillable = [
'code',
'categorie_id',
'content',
'sort',
'status',
'user_id'
];
public function attachments(){
return $this->hasMany(Attachment::class, 'ticket_id', 'id');
}
public function categorie(){
return $this->belongsTo(Categorie::class, 'categorie_id', 'id');
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace app\ticket\service;
use app\queue\translation\service\TranslationService;
use app\queue\utils\Translat;
use app\ticket\logic\CategorieLogic;
use app\ticket\model\CategorieTranslation;
use support\Db;
use support\exception\BusinessException;
class CategorieService
{
public function list($params)
{
$result = (new CategorieLogic())->list($params);
$value=$result[0]->toArray();
foreach ($result as $key => &$value) {
$value['name'] = isset($value['translation']) ? $value['translation']['name'] : '';
unset($value['translation']);
}
unset($value);
return $result;
}
public function getAll($params)
{
$result = (new CategorieLogic())->getAll($params);
foreach ($result as $key => &$value) {
$value['name'] = isset($value['translation']) ? $value['translation']['name'] : '';
unset($value['translation']);
}
unset($value);
return $result;
}
public $translatFields = ['name'];
public function add(array $data)
{
try {
Db::beginTransaction();
$sourceLocale = locale();
$traitsourceFields = Translat::extractSourceFields($data, $this->translatFields);
$result = (new CategorieLogic())->add($data);
(new TranslationService())->queueTranslations(
'ticket_categorie',
$result->id,
$sourceLocale,
$traitsourceFields
);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public function edit(array $data)
{
try {
Db::beginTransaction();
$sourceLocale = locale();
$traitsourceFields = Translat::extractSourceFields($data, $this->translatFields);
$result = (new CategorieLogic())->edit($data);
// (new TranslationService())->queueTranslations(
// 'ticket_categorie',
// $data['id'],
// $sourceLocale,
// $traitsourceFields
// );
CategorieTranslation::updateOrCreate(
[
'categorie_id' => $data['id'],
'locale' => $sourceLocale,
],
$traitsourceFields
);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public function delete(int $id)
{
if(empty($id)){
throw new BusinessException('id不能为空');
}
try {
$result = (new CategorieLogic())->delete($id);
return $result;
} catch (\Throwable $e) {
throw $e;
}
}
}
@@ -0,0 +1,49 @@
<?php
namespace app\ticket\service;
use app\ticket\logic\ConversationLogic;
use config\constants\Constants;
use support\Db;
use support\exception\BusinessException;
class ConversationService
{
public function list($params)
{
$locale = locale();
// $locale = 'zh';
$ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN;
// $ticket_status = Constants::TICKET_STATUS_ZH;
// $params['locale'] = locale();
$result = (new ConversationLogic())->list($params);
$status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS));
foreach ($result as $key => $value) {
$result[$key]['content'] = str_replace($status_fanyi,$ticket_status,$value['content']);
}
return $result;
}
public function add(array $data)
{
try {
return (new ConversationLogic())->add($data);
} catch (\Throwable $e) {
throw $e;
}
}
public function edit(array $data)
{
try {
Db::beginTransaction();
$result = (new ConversationLogic())->edit($data);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace app\ticket\service;
use app\ticket\logic\AttachmentLogic;
use app\ticket\logic\ConversationLogic;
use app\ticket\logic\TicketLogic;
use app\ticket\model\Ticket;
use config\constants\Constants;
use support\Db;
use support\exception\BusinessException;
use support\Log;
class TicketService
{
public function list($params)
{
$res = (new TicketLogic())->list($params);
foreach ($res as $k => &$v) {
$name = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : '';
if(isset($res[$k]['categorie'])){
$res[$k]['categorie']['name'] = $name;
}
if (isset($v['categorie']['translation'])){
unset($res[$k]['categorie']['translation']);
}
foreach ($v['attachments'] as &$attachments) {
$attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']);
}
}
unset($v);
unset($attachments);
return $res;
}
public function add(array $data)
{
try {
$attachment = $data['attachment']??[];
$logic = new TicketLogic();
$result = $logic->add($data);
if(!empty($attachment)){
(new AttachmentLogic())->adds($attachment, $result->id, $data['user_id']);
}
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public function edit(array $data)
{
try {
Db::beginTransaction();
$attachment = $data['attachment']??[];
$logic = new TicketLogic();
$result = $logic->edit($data);
if(!empty($attachment)){
(new AttachmentLogic())->edit($attachment, $result->id, $data['user_id']);
}
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public function status(array $data)
{
try {
Db::beginTransaction();
$msg = $data['msg']??[];
$ticket = (new TicketLogic())->find($data['id']);
if(empty($ticket)){
throw new BusinessException('工单不存在');
}
if($data['status'] <= $ticket->status){
throw new BusinessException(trans('status_not_modified'));
}
// if(!in_array($ticket->status, [Constants::TICKET_STATUS['PENDING'], Constants::TICKET_STATUS['CONFIRM']])
// && empty($msg)){
// throw new BusinessException('提交失败');
// }
$ticket->status = $data['status'];
$result = $ticket->save();
if(!empty($msg)){
$conversation = [
'ticket_id' => $ticket->id,
'user_id' => $data['user_id'],
'content' => '状态更新为{{'.array_search($data['status'],Constants::TICKET_STATUS).'}}:'.$msg
];
$res = (new ConversationLogic())->add($conversation);
}
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public function getStatus(array $data)
{
$locale = locale();
$ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN;
$status = [];
foreach ($ticket_status as $key => $value) {
$status[] = ['value' => $key, 'text' => $value];
}
return $status;
}
}