This commit is contained in:
Aether
2025-09-19 17:10:05 +08:00
parent 211588d3ce
commit 0a6ba2c566
9 changed files with 489 additions and 3 deletions

2
.env
View File

@@ -9,7 +9,7 @@ DB_USERNAME=hyperf_data
DB_PASSWORD=4cfDRXZSksn7npiP DB_PASSWORD=4cfDRXZSksn7npiP
DB_CHARSET=utf8mb4 DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci DB_COLLATION=utf8mb4_unicode_ci
DB_PREFIX=da_ DB_PREFIX=
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_AUTH=(null) REDIS_AUTH=(null)

View File

@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use Aether\AetherController;
use Aether\AetherResponse;
use App\Exception\BusinessException;
use App\Model\Teacher;
use App\Validator\TeacherValidator;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
/**
* @Controller(prefix="/api/v1/teacher")
*/
class TeacherController extends AetherController
{
#[Inject]
protected TeacherValidator $validator;
/**
* 创建教师.
* @PostMapping(path="")
*/
public function create(RequestInterface $request): array
{
$data = $request->all();
$validator = $this->validator->validateCreate($data);
if ($validator->fails()) {
throw new BusinessException(400, $validator->errors()->first());
}
$teacher = new Teacher();
$teacher->fill($data);
$teacher->save();
return AetherResponse::success($teacher->toArray(), '教师创建成功');
}
/**
* 获取教师详情.
* @GetMapping(path="/{id}")
*/
public function get(int $id): array
{
$teacher = Teacher::find($id);
if (! $teacher) {
throw new BusinessException(10101);
}
return AetherResponse::success($teacher->toArray());
}
/**
* 更新教师信息.
* @PutMapping(path="/{id}")
*/
public function update(int $id, RequestInterface $request): array
{
$data = $request->all();
$data['id'] = $id;
$validator = $this->validator->validateUpdate($data);
if ($validator->fails()) {
throw new BusinessException(400, $validator->errors()->first());
}
$teacher = Teacher::find($id);
if (! $teacher) {
throw new BusinessException(10101);
}
$teacher->fill($data);
$teacher->save();
return AetherResponse::success($teacher->toArray(), '教师信息更新成功');
}
/**
* 删除教师.
* @DeleteMapping(path="/{id}")
*/
public function delete(int $id): array
{
$teacher = Teacher::find($id);
if (! $teacher) {
throw new BusinessException(10101);
}
$teacher->delete();
return AetherResponse::success(null, '教师删除成功');
}
/**
* 教师列表.
* @GetMapping(path="/list")
*/
public function list(RequestInterface $request): array
{
$data = $request->all();
$validator = $this->validator->validateQuery($data);
if ($validator->fails()) {
throw new BusinessException(400, $validator->errors()->first());
}
$query = Teacher::query();
if (! empty($data['id'])) {
$query->where('id', $data['id']);
}
if (! empty($data['name'])) {
$query->where('name', 'like', "%{$data['name']}%");
}
if (isset($data['campus_id'])) {
$query->where('campus_id', $data['campus_id']);
}
if (! empty($data['major_subject'])) {
$query->where('major_subject', 'like', "%{$data['major_subject']}%");
}
if (isset($data['status'])) {
$query->where('status', $data['status']);
}
$page = $data['page'] ?? 1;
$size = $data['size'] ?? 20;
$total = $query->count();
$list = $query->forPage($page, $size)->get()->toArray();
return AetherResponse::success([
'total' => $total,
'page' => $page,
'size' => $size,
'list' => $list,
]);
}
/**
* 搜索教师.
* @GetMapping(path="/search")
*/
public function search(RequestInterface $request): array
{
$keyword = $request->input('keyword', '');
$page = (int) $request->input('page', 1);
$size = (int) $request->input('size', 20);
$query = Teacher::search($keyword)->enabled();
$total = $query->count();
$list = $query->forPage($page, $size)->get()->toArray();
return AetherResponse::success([
'total' => $total,
'page' => $page,
'size' => $size,
'list' => $list,
]);
}
}

View File

@@ -38,6 +38,9 @@ class BusinessException extends ServerException
10002 => '校区已存在', 10002 => '校区已存在',
10003 => '父级校区不存在', 10003 => '父级校区不存在',
10004 => '层级参数错误', 10004 => '层级参数错误',
10101 => '教师不存在',
10102 => '教师已存在',
10103 => '该教师已有关联课程,无法删除',
]; ];
return $messages[$code] ?? '业务处理失败'; return $messages[$code] ?? '业务处理失败';

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\JsonRpc\Service;
use Aether\Exception\BusinessException;
use App\Model\Teacher;
use Hyperf\RpcServer\Annotation\RpcService;
use MicroService\Contract\TeacherServiceInterface;
#[RpcService(
name: 'DataTeacher',
server: 'jsonrpc-http',
protocol: 'jsonrpc-http',
publishTo: 'nacos'
)]
class TeacherService implements TeacherServiceInterface
{
public function getTeacherById(int $id): array
{
$teacher = Teacher::find($id);
if (! $teacher || $teacher->status != 1) {
throw new BusinessException('教师不存在或已禁用', 10101);
}
return $teacher->toArray();
}
public function getTeachersByIds(array $ids): array
{
return Teacher::whereIn('id', $ids)
->enabled()
->orderBy('sort_order', 'asc')
->get()
->toArray();
}
public function getTeachersByCampusId(int $campusId, int $page = 1, int $size = 20): array
{
$query = Teacher::campusId($campusId)
->enabled()
->orderBy('sort_order', 'asc');
$total = $query->count();
$list = $query->forPage($page, $size)->get()->toArray();
return [
'total' => $total,
'page' => $page,
'size' => $size,
'list' => $list,
];
}
public function getTeachersBySubject(string $subject, int $page = 1, int $size = 20): array
{
$query = Teacher::subject($subject)
->enabled()
->orderBy('sort_order', 'asc');
$total = $query->count();
$list = $query->forPage($page, $size)->get()->toArray();
return [
'total' => $total,
'page' => $page,
'size' => $size,
'list' => $list,
];
}
public function searchTeachers(string $keyword, int $page = 1, int $size = 20): array
{
$query = Teacher::search($keyword)
->enabled()
->orderBy('sort_order', 'asc');
$total = $query->count();
$list = $query->forPage($page, $size)->get()->toArray();
return [
'total' => $total,
'page' => $page,
'size' => $size,
'list' => $list,
];
}
}

View File

@@ -28,7 +28,7 @@ use Hyperf\Database\Model\Relations\HasMany;
*/ */
class Campus extends AetherModel class Campus extends AetherModel
{ {
protected ?string $table = 'campus'; protected ?string $table = 'da_campus';
protected array $fillable = [ protected array $fillable = [
'name', 'name',

106
app/Model/Teacher.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Model;
use Aether\AetherModel;
use Carbon\Carbon;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Relations\BelongsTo;
/**
* 教师模型.
* @property int $id
* @property string $name 教师姓名
* @property int $age 年龄
* @property int $gender 性别1-男2-女
* @property string $avatar 头像URL
* @property string $title 职称
* @property string $major_subject 主讲科目
* @property string $teaching_style 授课风格
* @property string $introduction 教师简介
* @property int $campus_id 所属校区ID
* @property int $status 状态0-禁用1-启用
* @property int $sort_order 排序
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Carbon $deleted_at
*/
class Teacher extends AetherModel
{
protected ?string $table = 'da_teacher';
protected array $fillable = [
'name',
'age',
'gender',
'avatar',
'title',
'major_subject',
'teaching_style',
'introduction',
'campus_id',
'status',
'sort_order',
];
protected array $casts = [
'id' => 'integer',
'age' => 'integer',
'gender' => 'integer',
'campus_id' => 'integer',
'status' => 'integer',
'sort_order' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
/**
* 所属校区.
*/
public function campus(): BelongsTo
{
return $this->belongsTo(Campus::class, 'campus_id', 'id')
->where('status', 1)
->whereNull('deleted_at');
}
/**
* 按校区查询.
*/
public function scopeCampusId(Builder $query, int $campusId): Builder
{
return $query->where('campus_id', $campusId);
}
/**
* 按科目查询.
*/
public function scopeSubject(Builder $query, string $subject): Builder
{
return $query->where('major_subject', 'like', "%{$subject}%");
}
/**
* 搜索教师.
*/
public function scopeSearch(Builder $query, string $keyword): Builder
{
return $query->where(function ($q) use ($keyword) {
$q->where('name', 'like', "%{$keyword}%")
->orWhere('title', 'like', "%{$keyword}%")
->orWhere('major_subject', 'like', "%{$keyword}%")
->orWhere('introduction', 'like', "%{$keyword}%");
});
}
/**
* 查询启用的教师.
*/
public function scopeEnabled(Builder $query): Builder
{
return $query->where('status', 1);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Validator;
use Hyperf\Validation\Contract\ValidatorFactoryInterface;
use Hyperf\Validation\Validator;
class TeacherValidator
{
public function __construct(protected ValidatorFactoryInterface $validationFactory)
{
}
public function validateCreate(array $data): Validator
{
return $this->validationFactory->make($data, [
'name' => 'required|string|max:50',
'age' => 'nullable|integer|min:18|max:65',
'gender' => 'nullable|integer|in:1,2',
'avatar' => 'nullable|url|max:255',
'title' => 'nullable|string|max:100',
'major_subject' => 'required|string|max:100',
'teaching_style' => 'nullable|string',
'introduction' => 'nullable|string',
'campus_id' => 'nullable|integer|min:0',
'status' => 'nullable|integer|in:0,1',
'sort_order' => 'nullable|integer|min:0',
], [
'name.required' => '教师姓名不能为空',
'major_subject.required' => '主讲科目不能为空',
'age.min' => '年龄不能小于18岁',
'age.max' => '年龄不能大于65岁',
'gender.in' => '性别只能是1(男)或2(女)',
'avatar.url' => '头像必须是有效的URL',
]);
}
public function validateUpdate(array $data): Validator
{
return $this->validationFactory->make($data, [
'id' => 'required|integer|min:1',
'name' => 'nullable|string|max:50',
'age' => 'nullable|integer|min:18|max:65',
'gender' => 'nullable|integer|in:1,2',
'avatar' => 'nullable|url|max:255',
'title' => 'nullable|string|max:100',
'major_subject' => 'nullable|string|max:100',
'teaching_style' => 'nullable|string',
'introduction' => 'nullable|string',
'campus_id' => 'nullable|integer|min:0',
'status' => 'nullable|integer|in:0,1',
'sort_order' => 'nullable|integer|min:0',
], [
'id.required' => '教师ID不能为空',
'age.min' => '年龄不能小于18岁',
'age.max' => '年龄不能大于65岁',
'gender.in' => '性别只能是1(男)或2(女)',
'avatar.url' => '头像必须是有效的URL',
]);
}
public function validateQuery(array $data): Validator
{
return $this->validationFactory->make($data, [
'id' => 'nullable|integer|min:1',
'name' => 'nullable|string|max:50',
'campus_id' => 'nullable|integer|min:0',
'major_subject' => 'nullable|string|max:100',
'status' => 'nullable|integer|in:0,1',
'page' => 'nullable|integer|min:1',
'size' => 'nullable|integer|min:1|max:100',
'keyword' => 'nullable|string|max:100',
]);
}
}

View File

@@ -48,5 +48,19 @@ return [
// 'heartbeat' => 5, // 'heartbeat' => 5,
// 'heartbeat_timeout' => 15, // 'heartbeat_timeout' => 15,
// ], // ],
[
'name' => 'DataTeacher',
'host' => env('NACOS_HOST', '192.168.28.199'),
'port' => env('NACOS_PORT', 8848),
'username' => env('NACOS_USERNAME', 'nacos'),
'password' => env('NACOS_PASSWORD', 'nacos'),
'namespace' => env('NACOS_NAMESPACE', 'e42b853c-5195-478b-b5e3-6d49f6a45053'),
'group_name' => env('NACOS_GROUP', 'DEFAULT_GROUP'),
'service_name' => 'DataTeacher',
'metadata' => [
'protocol' => 'jsonrpc-http',
'server' => 'jsonrpc-http',
],
],
// ], // ],
]; ];

View File

@@ -1,8 +1,44 @@
<?php <?php
declare(strict_types=1);
namespace MicroService\Contract; namespace MicroService\Contract;
interface TeacherServiceInterface interface TeacherServiceInterface
{ {
/**
* 获取教师详情.
* @param int $id 教师ID
*/
public function getTeacherById(int $id): array;
} /**
* 批量获取教师信息.
* @param array $ids 教师ID列表
*/
public function getTeachersByIds(array $ids): array;
/**
* 根据校区获取教师列表.
* @param int $campusId 校区ID
* @param int $page 页码
* @param int $size 每页条数
*/
public function getTeachersByCampusId(int $campusId, int $page = 1, int $size = 20): array;
/**
* 根据科目获取教师列表.
* @param string $subject 科目名称
* @param int $page 页码
* @param int $size 每页条数
*/
public function getTeachersBySubject(string $subject, int $page = 1, int $size = 20): array;
/**
* 搜索教师.
* @param string $keyword 搜索关键词
* @param int $page 页码
* @param int $size 每页条数
*/
public function searchTeachers(string $keyword, int $page = 1, int $size = 20): array;
}