| 5 |
cakephp5 登录 |
<p>1、建表</p>
<pre class=""><code class="hljs language-sql">CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);</code></pre>
<p> </p>
<p>2、在有composer.json的目录下(通常为根目录)执行</p>
<pre class=""><code class="hljs language-bash">composer require cakephp/authentication</code></pre>
<p> </p>
<p>3、在当前cake项目的根目录下执行</p>
<pre class=""><code class="hljs language-bash">bin/cake bake all users</code></pre>
<p> </p>
<p>4、在 src/Model/Entity/User.php 中 添加一句话和一个方法使密码加密</p>
<pre class=""><code class="language-php hljs">use Authentication\PasswordHasher\DefaultPasswordHasher;</code></pre>
<pre class=""><code class="hljs language-php">protected function _setPassword(string $password) : ?string
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
return null;
}</code></pre>
<p> </p>
<p>5、修改src/Application.php文件</p>
<pre class=""><code class="language-php hljs">//添加导入
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;</code></pre>
<p> </p>
<pre class=""><code class="language-php hljs">//添加接口实现implements AuthenticationServiceProviderInterface
class Application extends BaseApplication implements AuthenticationServiceProviderInterface</code></pre>
<p> </p>
<pre class=""><code class="language-php hljs">//修改方法,在middleware方法中 找到->add(new BodyParserMiddleware())这句话 在他下面添加一句
->add(new AuthenticationMiddleware($this))</code></pre>
<p> </p>
<pre class=""><code class="language-php hljs">//添加方法
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => Router::url('/users/login'),
'identifier' => [
'Authentication.Password' => [
'fields' => [
'username' => 'email',
'password' => 'password',
],
],
],
]);
return $authenticationService;
}</code></pre>
<p> </p>
<p>6、在src/Controller/AppController.php中添加</p>
<pre class=""><code class="language-php hljs">$this->loadComponent('Authentication.Authentication');</code></pre>
<p> </p>
<p>7、在 UsersController 中添加</p>
<pre class=""><code class="language-php hljs">public function login()
{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result && $result->isValid()) {
// redirect to /articles after login success
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Articles',
'action' => 'index',
]);
return $this->redirect($redirect);
}
// display error if user submitted and authentication failed
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid username or password'));
}
}
public function beforeFilter(\Cake\Event\EventInterface $event): void
{
parent::beforeFilter($event);
// Configure the login action to not require authentication, preventing
// the infinite redirect loop issue
$this->Authentication->addUnauthenticatedActions(['login']);
}
</code></pre>
<p> </p>
<p>8、创建/templates/Users/login.php</p>
<pre class=""><code class="language-php hljs"><div class="users form">
<?= $this->Flash->render() ?>
<h3>Login</h3>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('Please enter your username and password') ?></legend>
<?= $this->Form->control('email', ['required' => true]) ?>
<?= $this->Form->control('password', ['required' => true]) ?>
</fieldset>
<?= $this->Form->submit(__('Login')); ?>
<?= $this->Form->end() ?>
<?= $this->Html->link("Add User", ['action' => 'add']) ?>
</div></code></pre>
<p> </p>
<p>9、在src/Controller/AppController.php中添加以下代码,以开放对应页面不用登录即可访问</p>
<pre class=""><code class="language-php hljs">public function beforeFilter(\Cake\Event\EventInterface $event): void
{
parent::beforeFilter($event);
// for all controllers in our application, make index and view
// actions public, skipping the authentication check
$this->Authentication->addUnauthenticatedActions(['index', 'view']);
}</code></pre>
<p> </p>
<p>10、注销,在src/Controller/UsersController.php添加</p>
<pre class=""><code class="language-php hljs">public function logout()
{
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result && $result->isValid()) {
$this->Authentication->logout();
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
}
}</code></pre>
<p> </p> |
1 |
3 |
1 |
1/7/26, 7:30 AM |
1/7/26, 7:38 AM |
View Edit Delete |
| 20 |
cakephp5 连ElasticSearch |
<p>1、在es里面先添加好规则</p>
<pre class=""><code class="language-json hljs">{
"mappings":{
"properties":{
"id":{
"type":"keyword"
},
"title":{
"type":"text",
"analyzer":"ik_max_word",
"copy_to":"all"
},
"content":{
"type":"text",
"analyzer":"ik_max_word",
"copy_to":"all"
},
"framework_id":{
"type":"keyword"
},
"framework_name":{
"type":"text",
"analyzer":"ik_max_word",
"copy_to":"all"
},
"published": {
"type": "boolean"
},
"user_id": {
"type": "keyword"
},
"created": {
"type": "date"
},
"modified": {
"type": "date"
},
"all":{
"type":"text",
"analyzer":"ik_max_word"
}
}
}
}</code></pre>
<p> </p>
<p>2、安装ES插件,在项目根目录下执行</p>
<pre class=""><code class="hljs language-bash">composer require cakephp/elastic-search</code></pre>
<p> </p>
<p>3、加载插件,在src/Application.php文件的bootstrap()方法的parent::bootstrap();下面添加</p>
<pre class=""><code class="language-php hljs">$this->addPlugin('Cake/ElasticSearch');</code></pre>
<p> </p>
<p>4、配置ES帐号密码config/app_local.php的Datasources下,添加下面这段话,帐号密码端口自行替换</p>
<pre class=""><code class="language-php hljs"> 'elastic' => [
'className' => 'Cake\ElasticSearch\Datasource\Connection',
'driver' => 'Cake\ElasticSearch\Datasource\Connection',
'hosts' => ['http://elastic:A3MZa=H5sjtyIyE+7aSr@localhost:9201'],
],</code></pre>
<p> </p>
<p>5、在对应的模型层里面,添加afterSave和afterDelete代码,对应字段自行替换</p>
<pre class=""><code class="hljs language-php"> public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options){
try {
// 连接ElasticSearch
$notesIndex = $this->getElasticsearchIndex();
// 定义数据
$data = [
'id' => (string)$entity ->get('id'),
'title' => $entity ->get('title'),
'content' => $entity ->get('content'),
'framework_id' => $entity ->get('framework_id'),
'published' => $entity ->get('published'),
'user_id' => $entity ->get('user_id'),
'created' => $entity ->get('created'),
'modified' => $entity ->get('modified'),
];
if (!$entity->has('framework') && $entity->framework_id) {
// 通过 loadInto 方法加载关联
$entity = $this->loadInto($entity, ['Frameworks']);
}
if ($entity->has('framework') && !empty($entity->framework)) {
$data['framework_name'] = $entity->framework->name;
} else if ($entity->framework_id) {
// 如果关联加载失败,可以直接查询数据库获取框架名
$framework = $this->Frameworks->find()
->select(['name'])
->where(['id' => $entity->framework_id])
->first();
if ($framework) {
$data['framework_name'] = $framework->name;
}
}
// 保存数据
$document = $notesIndex->newEntity($data);
if ($notesIndex->save($document)) {
Log::info("Note ID {$entity->id} 同步到 Elasticsearch 成功");
} else {
Log::error("Note ID {$entity->id} 同步到 Elasticsearch 失败");
}
} catch (Exception $e) {
Log::error("Elasticsearch 同步异常,Note ID: {$entity->id}, 错误: " . $e->getMessage());
}
}
/**
* 在删除笔记后触发,用于从 Elasticsearch 删除文档
*
* @param \Cake\Event\EventInterface $event
* @param \Cake\Datasource\EntityInterface $entity
* @param \ArrayObject $options
* @return void
*/
public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
try {
// 连接ElasticSearch
$notesIndex = $this->getElasticsearchIndex();
try {
$document = $notesIndex->get((string)$entity->id);
if ($notesIndex->delete($document)) {
Log::info("ID {$entity->id} 删除成功");
} else {
Log::warning("⚠️ 删除操作返回 false,Note ID: {$entity->id}");
}
} catch (\Cake\Datasource\Exception\RecordNotFoundException $e) {
// 文档不存在,无需删除
Log::debug("Note ID {$entity->id} 在 Elasticsearch 索引中不存在,无需删除");
}
} catch (MissingDocumentException $e) {
Log::debug("Note ID {$entity->id} 在 Elasticsearch 索引中不存在,无需删除");
} catch (Exception $e) {
// 捕获其他所有可能的异常(如连接失败、语法错误等)
Log::error("🔥 从 Elasticsearch 删除文档时发生异常,Note ID: {$entity->id}");
Log::error("错误信息: " . $e->getMessage());
}
}
private function getElasticsearchIndex()
{
// 单例模式,避免重复创建连接
static $notesIndex = null;
if ($notesIndex === null) {
$connection = ConnectionManager::get('elastic');
$notesIndex = new Index([
'connection' => $connection,
'name' => 'notes'
]);
}
return $notesIndex;
}</code></pre>
<p> </p>
<p>6、关于搜索,目前cake官方插件where条件只存在and不能用or。所以自己写了一下。还没优化,先凑活用。</p>
<pre class=""><code class="hljs language-php"> public function search()
{
$this->request->allowMethod(['get']);
$keyword = $this->request->getQuery('q');
$framework_id = $this->request->getQuery('framework_id');
$page = $this->request->getQuery('page', 1);
$limit = $this->request->getQuery('limit', 10);
// 检查Datasources配置
$requestUrl = ConnectionManager::get('elastic')->config()['hosts'][0] . '/notes/_search';
$from = ($page - 1) * $limit;
$query = [
'query' => [
'bool' => [
'must' => [
['term' => ['published' => true]]
]
]
],
'from' => $from,
'size' => $limit,
'sort' => [
[
'created' => [
'order' => 'desc'
]
]
]
];
// 添加搜索条件
if (!empty($framework_id)) {
$query['query']['bool']['must'][] = ['term' => ['framework_id' => $framework_id]];
}
if (!empty($keyword)) {
$query['query']['bool']['must'][] = ['match' => ['all' => $keyword]];
}
// echo "请求体: " . json_encode($query, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "<br>";
// 解析URL获取认证信息
$parsedUrl = parse_url($requestUrl);
// 重新构建不带认证信息的URL
$cleanUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . ':' . $parsedUrl['port'] . $parsedUrl['path'];
// 发送POST请求(CakePHP HttpClient的GET请求不支持请求体)
$http = new Client();
$options = [
'type' => 'json',
'headers' => ['Content-Type' => 'application/json']
];
// 添加认证头部
if (isset($parsedUrl['user']) && isset($parsedUrl['pass'])) {
$auth = base64_encode($parsedUrl['user'] . ':' . $parsedUrl['pass']);
$options['headers']['Authorization'] = 'Basic ' . $auth;
}
// 发送POST请求
$response = $http->post($cleanUrl, json_encode($query), $options);
if ($response->isOk()) {
$data = json_decode($response->getStringBody(),true);
// pr($data);
// 处理响应数据
$notes = [];
$total = $data['hits']['total']['value'] ?? 0;
if (isset($data['hits']['hits'])) {
foreach ($data['hits']['hits'] as $hit) {
$note = $hit['_source'];
$note['id'] = $hit['_id'];
$notes[] = $note;
}
}
// 创建分页数据
$paging = [
'count' => $total,
'current' => count($notes),
'perPage' => $limit,
'page' => $page,
'pages' => $total > 0 ? ceil($total / $limit) : 1,
'prevPage' => $page > 1,
'nextPage' => ($page * $limit) < $total,
'start' => $from + 1,
'end' => min($from + $limit, $total)
];
// 7. 设置视图变量
$this->set(compact('notes', 'paging','page','limit','total'));
$this->viewBuilder()->setOption('serialize', ['notes', 'paging']);
}
}</code></pre>
<p> </p> |
1 |
3 |
1 |
1/13/26, 8:34 AM |
1/22/26, 10:42 AM |
View Edit Delete |