This commit is contained in:
2025-11-18 13:38:05 +08:00
parent a3e5568f93
commit f9062837ab
7 changed files with 286 additions and 116 deletions

View File

@@ -14,8 +14,7 @@ class CommonPhraseController extends ApiController
*/
public function index(Request $request): Response
{
$uid = $request->get('uid');
return $this->success(Db::name('chat_common_phrase')->where('user_id', $uid)
return $this->success(Db::name('chat_common_phrase')->where('user_id', $this->uid)
->order('sort', 'desc')
->field(['id', 'content', 'sort', 'created_time'])
->select());
@@ -26,19 +25,18 @@ class CommonPhraseController extends ApiController
*/
public function store(Request $request): Response
{
$uid = $request->post('uid');
$content = $request->post('content', '');
$sort = $request->post('sort', 0);
if (empty($content) || mb_strlen($content) > 500) {
return $this->error('常用语内容不能为空且长度≤500字');
}
// 有重复的内容不给添加
$msg = Db::name('chat_common_phrase')->where(['user_id' => $uid, 'content' => $content])->find();
$msg = Db::name('chat_common_phrase')->where(['user_id' => $this->uid, 'content' => $content])->find();
if($msg) {
return $this->error('此内容已经存在,不能重复添加');
}
$res = Db::name('chat_common_phrase')->insert([
'user_id' => $uid,
'user_id' => $this->uid,
'content' => $content,
'sort' => $sort,
'created_time' => d(),
@@ -53,15 +51,24 @@ class CommonPhraseController extends ApiController
/**
* 删除常用语
*/
public function destroy(Request $request, int $id): Response
public function destroy(Request $request): Response
{
$uid = $request->uid;
$phrase = ChatCommonPhrase::where(['id' => $id, 'user_id' => $uid])->find();
$id = $request->post('id');
$phrase = Db::name('chat_common_phrase')->where([
'user_id' => $this->uid,
'id' => $id,
])->find();
if (!$phrase) {
return json(['code' => 404, 'msg' => '常用语不存在']);
return $this->error('常用语不存在');
}
$phrase_del = Db::name('chat_common_phrase')->where([
'user_id' => $this->uid,
'id' => $id,
])->delete();
if($phrase_del) {
return $this->success();
}else {
return $this->error();
}
$phrase->delete();
return json(['code' => 200, 'msg' => '删除成功']);
}
}

View File

@@ -5,60 +5,64 @@ use app\chat\model\ChatGroup;
use app\chat\model\ChatGroupMember;
use app\chat\model\ChatGroupMute;
use app\chat\model\ChatDoNotDisturb;
use app\common\controller\ApiController;
use app\utils\Session;
use support\Request;
use support\Response;
use support\think\Db;
class GroupController
class GroupController extends ApiController
{
/**
* 创建群(仅商家可创建)
*/
public function create(Request $request): Response
{
$uid = $request->uid;
$userType = $request->user_type;
if ($userType != 2) { // 2=商家
return json(['code' => 403, 'msg' => '仅商家可创建群聊']);
if ($this->user_type == 1) { // 2=商家
return $this->error('仅商家可创建群聊');
}
$name = $request->post('name', '');
$avatar = $request->post('avatar', '');
$shop_id = $request->post('shop_id', 0);
if(empty($shop_id)) {
return $this->error('参数不完整');
}
$shop = Db::name('tb_shop_info')->where(['id' => $shop_id])->find();
if(empty($shop)) {
return $this->error('店铺不存在');
}
// 查找群数量
$count = Db::name('chat_group')->where(['shop_id' => $shop_id])->count();
$name = $request->post('name')?:$shop['shop_name'] . '粉丝福利' . $count + 1 . '群';
$avatar = $request->post('avatar')?:$shop['logo'];
$announcement = $request->post('announcement', '');
$isPublic = $request->post('is_public', 0);
if (empty($name)) {
return json(['code' => 400, 'msg' => '群名称不能为空']);
}
$now = time();
$now = d();
try {
Db::startTrans();
// 创建群
$group = ChatGroup::create([
$group_id = Db::name('chat_group')->insertGetId([
'name' => $name,
'shop_id' => $shop_id,
'avatar' => $avatar,
'owner_id' => $uid,
'owner_id' => $this->uid,
'announcement' => $announcement,
'is_public' => $isPublic,
'created_at' => $now,
'updated_at' => $now
'created_time' => $now,
]);
// 群主加入群
ChatGroupMember::create([
'group_id' => $group->id,
'user_id' => $uid,
Db::name('chat_group_member')->insert([
'group_id' => $group_id,
'user_id' => $this->uid,
'role' => 1, // 1=群主
'join_time' => $now,
'quit_time' => null,
'is_kicked' => 0
]);
Db::commit();
return $this->success(['group_id' => $group_id]);
}catch (\Throwable $exception) {
Db::rollback();
return $this->error($exception->getMessage());
}
return json([
'code' => 200,
'msg' => '群创建成功',
'data' => ['group_id' => $group->id]
]);
}
/**
@@ -66,92 +70,129 @@ class GroupController
*/
public function join(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
$group_id = $request->post('group_id');
$invite = $request->post('invite');
if (!$group_id) {
return $this->error('缺少group_id');
}
// 验证群是否存在
$group = ChatGroup::find($groupId);
$group = Db::name('chat_group')->where(['id' => $group_id])->find();
if (!$group) {
return json(['code' => 404, 'msg' => '群不存在']);
return $this->error('群不存在');
}
// 验证是否已在群内
$exists = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
$exists = Db::name('chat_group_member')->where([
'group_id' => $group_id,
'user_id' => $this->uid,
'quit_time' => null,
'is_kicked' => 0
])->exists();
])->find();
if ($exists) {
return json(['code' => 400, 'msg' => '已在群内']);
return $this->error('已在群内');
}
// 被踢用户不能重新加入(需群主邀请)
$isKicked = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
$isKicked = Db::name('chat_group_member')->where([
'group_id' => $group_id,
'user_id' => $this->uid,
'is_kicked' => 1
])->exists();
if ($isKicked) {
return json(['code' => 403, 'msg' => '你已被移出该群,无法重新加入']);
])->find();
// 如果不是邀请
if(empty($invite) && $isKicked) {
return $this->error('你已被移出该群,无法重新加入');
}
$insert_arr = [
'group_id' => $group_id,
'user_id' => $this->uid,
'role' => 3,
'join_time' => d(),
];
$is_insert = true;
if($invite) {
// 验证邀请参数是否正常
$decryptedText = simple_decrypt($invite, config('cons.sercer_key'));
$decryptedText_arr = json_decode($decryptedText, true);
if($isKicked) {
$is_insert = false;
$insert_arr['is_kicked'] = 0;
}
$insert_arr['pid'] = $decryptedText_arr['pid'];
$insert_arr['group_id'] = $decryptedText_arr['group_id'];
$group_id = $decryptedText_arr['group_id'];
}
// 加入群
ChatGroupMember::create([
'group_id' => $groupId,
'user_id' => $uid,
'role' => 3, // 3=普通成员
'join_time' => time(),
'quit_time' => null,
'is_kicked' => 0
]);
return json(['code' => 200, 'msg' => '加群成功']);
if($is_insert) {
$res = Db::name('chat_group_member')->insert($insert_arr);
}else {
$res = Db::name('chat_group_member')->where(['user_id' => $this->uid, 'group_id' => $group_id])->update($insert_arr);
}
if($res) {
return $this->success();
}else {
return $this->error();
}
}
// 获取群邀请链接参数
public function getgrepurl(Request $request): Response
{
$group_id = $request->post('group_id');
if (!$group_id) {
return $this->error('缺少group_id');
}
if($this->user_type == 1) {
return $this->error('角色有误');
}
$data = [
'pid' => $this->uid,
'group_id' => $group_id
];
return $this->success(simple_encrypt(json_encode($data), config('cons.sercer_key')));
}
/**
* 退群
*/
public function quit(Request $request): Response
{
$uid = $request->uid;
$groupId = $request->post('group_id', 0);
$groupId = $request->post('group_id')?:0;
if (!$groupId) {
return json(['code' => 400, 'msg' => '缺少group_id']);
return $this->error('缺少group_id');
}
// 验证是否是群主(群主不能退群,需转让)
$isOwner = ChatGroupMember::where([
$owner = Db::name('chat_group_member')->where([
'group_id' => $groupId,
'user_id' => $uid,
'role' => 1,
'user_id' => $this->uid,
'quit_time' => null
])->exists();
if ($isOwner) {
return json(['code' => 403, 'msg' => '群主不能退群,请先转让群主']);
}
// 标记退出时间
$member = ChatGroupMember::where([
'group_id' => $groupId,
'user_id' => $uid,
'quit_time' => null,
'is_kicked' => 0
])->find();
if (!$member) {
return json(['code' => 400, 'msg' => '不在群内']);
if ($owner) {
if($owner['role'] == 1) {
return $this->error('群主不能退群,请先转让群');
}
}else {
return $this->error('不在群内');
}
$res = Db::name('chat_group_member')->where([
'group_id' => $groupId,
'user_id' => $this->uid,
])->update(['quit_time' => d()]);
if($res) {
return $this->success();
}
return $this->error();
}
$member->quit_time = time();
$member->save();
return json(['code' => 200, 'msg' => '退群成功']);
}
/**
* 设置群公告(仅群主/管理员)

View File

@@ -5,6 +5,14 @@ use app\exception\MyBusinessException;
use support\exception\BusinessException;
class ApiController
{
public $uid;
public $user_type;
public function __construct()
{
$this->uid = input('uid');
$this->user_type = input('user_type');
}
public function success($data = [])
{

View File

@@ -44,6 +44,114 @@ if (!function_exists('d')) {
/**
* 加密函数带随机字符AES-256-CBC
* @param string $plaintext 待加密明文(如字符串、数字)
* @param string $key 加密密钥(建议至少 8 位,越复杂越安全)
* @return string|false 加密后的密文(失败返回 false
*/
function simple_encrypt(string $plaintext, string $key): string|false
{
try {
// 1. 生成随机字符Salt + IV
$salt = random_bytes(8); // 8 位随机盐值(增加密钥多样性)
$iv = random_bytes(16); // 16 位随机 IVAES-256-CBC 块大小要求)
// 2. 密钥处理Salt + 原始密钥 → SHA256 哈希 → 32 位密钥AES-256 要求)
$encryptedKey = hash('sha256', $salt . $key, true); // true 表示返回二进制数据
// 3. 明文填充PKCS7 填充AES 要求明文长度是块大小的整数倍)
$blockSize = openssl_cipher_iv_length('aes-256-cbc');
$padding = $blockSize - (strlen($plaintext) % $blockSize);
$paddedPlaintext = $plaintext . str_repeat(chr($padding), $padding);
// 4. AES 加密(返回 Base64 编码的密文)
$ciphertext = openssl_encrypt(
$paddedPlaintext,
'aes-256-cbc',
$encryptedKey,
OPENSSL_RAW_DATA, // 输出原始二进制数据(后续手动 Base64
$iv
);
if ($ciphertext === false) {
throw new Exception('加密失败:' . openssl_error_string());
}
// 5. 拼接 Salt + IV + 密文Base64 编码后用冒号分隔,避免字符冲突)
return base64_encode($salt) . ':' . base64_encode($iv) . ':' . base64_encode($ciphertext);
} catch (Exception $e) {
error_log('加密异常:' . $e->getMessage());
return false;
}
}
/**
* 解密函数(对应 simple_encrypt
* @param string $ciphertext 加密后的密文
* @param string $key 解密密钥(必须与加密密钥一致)
* @return string|false 解密后的明文(失败返回 false
*/
function simple_decrypt(string $ciphertext, string $key): string|false
{
try {
// 1. 拆分密文Salt:IV:密文(按冒号分割)
$parts = explode(':', $ciphertext);
if (count($parts) !== 3) {
throw new Exception('密文格式错误');
}
// 2. Base64 解码,获取原始 Salt、IV、密文
$salt = base64_decode($parts[0]);
$iv = base64_decode($parts[1]);
$ciphertextRaw = base64_decode($parts[2]);
if ($salt === false || $iv === false || $ciphertextRaw === false) {
throw new Exception('Base64 解码失败');
}
// 3. 密钥处理(与加密时一致)
$encryptedKey = hash('sha256', $salt . $key, true);
// 4. AES 解密
$paddedPlaintext = openssl_decrypt(
$ciphertextRaw,
'aes-256-cbc',
$encryptedKey,
OPENSSL_RAW_DATA,
$iv
);
if ($paddedPlaintext === false) {
throw new Exception('解密失败:' . openssl_error_string());
}
// 5. 去除 PKCS7 填充
$padding = ord(substr($paddedPlaintext, -1));
$plaintext = substr($paddedPlaintext, 0, -$padding);
return $plaintext;
} catch (Exception $e) {
error_log('解密异常:' . $e->getMessage());
return false;
}
}
/**
* 多维数组去重并重新索引
* @param array $array 待处理的多维数组

View File

@@ -15,14 +15,18 @@ class JwtAuthMiddleware implements MiddlewareInterface
{
$uid = Redis::get('token:client:token:' . $request->header('token'));
if($uid) {
if($request->isGet()) {
$request->setGet('uid', $uid);
}elseif ($request->post()) {
$request->setPost('uid', $uid);
}
}else {
// 用户
$user_type = 1;
}else{
$uid = Redis::get('token:admin:token:' . $request->header('token'));
if(!$uid) {
throw new MyBusinessException('请登录', 3000);
}
// 商家
$user_type = 2;
}
$request->setPost('uid', $uid);
$request->setPost('user_type', $user_type);
return $handler($request);
}
}

View File

@@ -19,5 +19,6 @@ return [
'user' => 'chaozg',
'password' => 'chaozg123',
'queue_t' => 'dev',
]
],
'sercer_key' => 'JVt1ZT5gwRqHR8pR7cX6DFvf8BxgKugB'
];

View File

@@ -20,31 +20,32 @@ Route::group('/api/chat', function () {
$jwt_middleware = app\middleware\JwtAuthMiddleware::class;
// 常用语管理
Route::group('/common-phrase', function () {
Route::get('', app\chat\controller\CommonPhraseController::class . '@index'); // 列表
Route::post('', app\chat\controller\CommonPhraseController::class . '@index'); // 列表
Route::post('/add', app\chat\controller\CommonPhraseController::class . '@store'); // 添加
Route::post('/del/{id}', app\chat\controller\CommonPhraseController::class . '@destroy'); // 删除
Route::post('/del', app\chat\controller\CommonPhraseController::class . '@destroy'); // 删除
})->middleware($jwt_middleware);
// 群聊管理
Route::group('/group', function () {
Route::post('/create', app\chat\controller\GroupController::class . '@create'); // 创建群(商家)
Route::post('/join', app\chat\controller\GroupController::class . '@join'); // 加群
Route::post('/getgrepurl', app\chat\controller\GroupController::class . '@getgrepurl'); // 群邀请参数
Route::post('/quit', app\chat\controller\GroupController::class . '@quit'); // 退群
Route::post('/announcement', app\chat\controller\GroupController::class . '@setAnnouncement'); // 群公告
Route::post('/do-not-disturb', app\chat\controller\GroupController::class . '@setDoNotDisturb'); // 免打扰
Route::post('/mute', app\chat\controller\GroupController::class . '@muteMember'); // 禁言
Route::post('/unmute', app\chat\controller\GroupController::class . '@unmuteMember'); // 解除禁言
Route::post('/kick', app\chat\controller\GroupController::class . '@kickMember'); // 踢人
Route::get('/members', app\chat\controller\GroupController::class . '@getMembers'); // 群成员列表
Route::post('/members', app\chat\controller\GroupController::class . '@getMembers'); // 群成员列表
})->middleware($jwt_middleware);
// 消息管理
Route::group('/message', function () {
Route::get('/history', app\chat\controller\MessageController::class . '@history'); // 历史消息
Route::post('/history', app\chat\controller\MessageController::class . '@history'); // 历史消息
Route::post('/mark-read', app\chat\controller\MessageController::class . '@markRead'); // 标记已读
Route::post('/mark-read-all', app\chat\controller\MessageController::class . '@markReadAll'); // 全部已读
Route::get('/unread-count', app\chat\controller\MessageController::class . '@getUnreadCount'); // 未读总数
Route::get('/session-list', app\chat\controller\MessageController::class . '@getSessionList'); // 会话列表
Route::post('/unread-count', app\chat\controller\MessageController::class . '@getUnreadCount'); // 未读总数
Route::post('/session-list', app\chat\controller\MessageController::class . '@getSessionList'); // 会话列表
})->middleware($jwt_middleware);
// 图片上传(相册/拍照)