Compare commits
4 Commits
e7bb34e11d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22947a56a4 | ||
|
|
20a68ddc8a | ||
|
|
d1e0f43992 | ||
|
|
e3ec1b0ca1 |
File diff suppressed because it is too large
Load Diff
@@ -242,70 +242,54 @@ class ExpertFinder extends Base
|
|||||||
// ==================== Cron / Auto Fetch ====================
|
// ==================== Cron / Auto Fetch ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Daily cron: auto-fetch experts for every journal's fields via queue
|
* Daily cron: auto-fetch experts for all active fields in t_expert_fetch via queue.
|
||||||
|
* No longer tied to journals; t_expert_fetch is the sole source of crawl targets.
|
||||||
*/
|
*/
|
||||||
public function dailyFetchAll()
|
public function dailyFetchAll()
|
||||||
{
|
{
|
||||||
$journalId = intval($this->request->param('journal_id', 0));
|
$perPage = max(10, intval($this->request->param('per_page', 200)));
|
||||||
$perPage = max(10, intval($this->request->param('per_page', 200)));
|
$source = $this->request->param('source', 'pubmed');
|
||||||
$source = $this->request->param('source', 'pubmed');
|
$minYear = intval($this->request->param('min_year', date('Y') - 3));
|
||||||
$minYear = intval($this->request->param('min_year', date('Y') - 3));
|
|
||||||
|
|
||||||
if ($journalId) {
|
$fetchList = Db::name('expert_fetch')
|
||||||
$journals = Db::name('journal')->field("journal_id,issn,title")->where('journal_id', $journalId)->select();
|
->where('state', 0)
|
||||||
} else {
|
->select();
|
||||||
$journals = Db::name('journal')->field("journal_id,issn,title")->where('state', 0)->select();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($journals)) {
|
if (empty($fetchList)) {
|
||||||
return jsonSuccess(['msg' => 'No active journals found', 'queued' => 0]);
|
return jsonSuccess(['msg' => 'No active fetch fields found', 'queued' => 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$queued = 0;
|
$queued = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$details = [];
|
$details = [];
|
||||||
$todayStart = strtotime(date('Y-m-d'));
|
$todayStart = strtotime(date('Y-m-d'));
|
||||||
foreach ($journals as $journal) {
|
|
||||||
$issn = trim($journal['issn'] ?? '');
|
|
||||||
if (empty($issn)) continue;
|
|
||||||
|
|
||||||
$majors = Db::name('major_to_journal')
|
foreach ($fetchList as $item) {
|
||||||
->alias('mtj')
|
$keyword = trim($item['field']);
|
||||||
->join('t_major m', 'm.major_id = mtj.major_id', 'left')
|
$itemSource = trim($item['source'] ?: $source);
|
||||||
->where('mtj.journal_issn', $issn)
|
if ($keyword === '') continue;
|
||||||
->where('mtj.mtj_state', 0)
|
|
||||||
->where("m.pid", "<>", 0)
|
|
||||||
->where('m.major_state', 0)
|
|
||||||
->column('m.major_title');
|
|
||||||
|
|
||||||
$majors = array_unique(array_filter($majors));
|
$fetchLog = $this->service->getFetchLog($keyword, $itemSource);
|
||||||
if (empty($majors)) continue;
|
if ($fetchLog['last_time'] >= $todayStart) {
|
||||||
foreach ($majors as $keyword) {
|
$skipped++;
|
||||||
$keyword = trim($keyword);
|
continue;
|
||||||
if (empty($keyword)) continue;
|
|
||||||
|
|
||||||
$fetchLog = $this->service->getFetchLog($keyword, $source);
|
|
||||||
if ($fetchLog['last_time'] >= $todayStart) {
|
|
||||||
$skipped++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$delay = $queued * 10;
|
|
||||||
\think\Queue::later($delay, 'app\api\job\FetchExperts@fire', [
|
|
||||||
'field' => $keyword,
|
|
||||||
'source' => $source,
|
|
||||||
'per_page' => $perPage,
|
|
||||||
'min_year' => $minYear,
|
|
||||||
'journal_id' => $journal['journal_id'],
|
|
||||||
], 'FetchExperts');
|
|
||||||
|
|
||||||
$queued++;
|
|
||||||
$details[] = [
|
|
||||||
'journal' => $journal['title'] ?? $journal['journal_id'],
|
|
||||||
'keyword' => $keyword,
|
|
||||||
'delay_s' => $delay,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$delay = $queued * 10;
|
||||||
|
\think\Queue::later($delay, 'app\api\job\FetchExperts@fire', [
|
||||||
|
'field' => $keyword,
|
||||||
|
'source' => $itemSource,
|
||||||
|
'per_page' => $perPage,
|
||||||
|
'min_year' => $minYear,
|
||||||
|
], 'FetchExperts');
|
||||||
|
|
||||||
|
$queued++;
|
||||||
|
$details[] = [
|
||||||
|
'expert_fetch_id' => $item['expert_fetch_id'],
|
||||||
|
'field' => $keyword,
|
||||||
|
'source' => $itemSource,
|
||||||
|
'delay_s' => $delay,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonSuccess([
|
return jsonSuccess([
|
||||||
@@ -371,7 +355,7 @@ class ExpertFinder extends Base
|
|||||||
], 'FetchExperts');
|
], 'FetchExperts');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mytest()
|
public function fetchOneField()
|
||||||
{
|
{
|
||||||
$data = $this->request->post();
|
$data = $this->request->post();
|
||||||
$rule = new Validate([
|
$rule = new Validate([
|
||||||
|
|||||||
643
application/api/controller/ExpertManage.php
Normal file
643
application/api/controller/ExpertManage.php
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use think\Db;
|
||||||
|
|
||||||
|
class ExpertManage extends Base
|
||||||
|
{
|
||||||
|
private $stateMap = [
|
||||||
|
0 => '待联系',
|
||||||
|
1 => '已发邮件',
|
||||||
|
2 => '已回复',
|
||||||
|
3 => '已投稿',
|
||||||
|
4 => '退信/无效',
|
||||||
|
5 => '黑名单(退订)',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(\think\Request $request = null)
|
||||||
|
{
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 专家列表(支持多条件筛选 + 分页)
|
||||||
|
*
|
||||||
|
* 参数:
|
||||||
|
* keyword - 搜索姓名/邮箱/单位
|
||||||
|
* field - 按领域关键词筛选
|
||||||
|
* major_id - 按学科ID筛选
|
||||||
|
* state - 状态筛选 (0-5, 传-1或不传则不过滤)
|
||||||
|
* source - 来源筛选
|
||||||
|
* pageIndex - 页码 (默认1)
|
||||||
|
* pageSize - 每页条数 (默认20)
|
||||||
|
*/
|
||||||
|
public function getList()
|
||||||
|
{
|
||||||
|
$data = $this->request->param();
|
||||||
|
$keyword = trim(isset($data['keyword']) ? $data['keyword'] : '');
|
||||||
|
$field = trim(isset($data['field']) ? $data['field'] : '');
|
||||||
|
$state = isset($data['state']) ? $data['state'] : '-1';
|
||||||
|
$source = trim(isset($data['source']) ? $data['source'] : '');
|
||||||
|
$page = max(1, intval(isset($data['pageIndex']) ? $data['pageIndex'] : 1));
|
||||||
|
$pageSize = max(1, intval(isset($data['pageSize']) ? $data['pageSize'] : 20));
|
||||||
|
|
||||||
|
$query = Db::name('expert')->alias('e');
|
||||||
|
$needJoin = ($field !== '');
|
||||||
|
|
||||||
|
if ($needJoin) {
|
||||||
|
$query->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner');
|
||||||
|
if ($field !== '') {
|
||||||
|
$query->where('ef.field', 'like', '%' . $field . '%');
|
||||||
|
}
|
||||||
|
$query->group('e.expert_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state !== '-1' && $state !== '') {
|
||||||
|
$query->where('e.state', intval($state));
|
||||||
|
}
|
||||||
|
if ($keyword !== '') {
|
||||||
|
$query->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%');
|
||||||
|
}
|
||||||
|
if ($source !== '') {
|
||||||
|
$query->where('e.source', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
$countQuery = clone $query;
|
||||||
|
$total = $countQuery->distinct('e.expert_id')->count();
|
||||||
|
|
||||||
|
$list = $query
|
||||||
|
->field('e.*')
|
||||||
|
->order('e.ctime desc')
|
||||||
|
->page($page, $pageSize)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
foreach ($list as &$item) {
|
||||||
|
$item['fields'] = Db::name('expert_field')
|
||||||
|
->where('expert_id', $item['expert_id'])
|
||||||
|
->where('state', 0)
|
||||||
|
->select();
|
||||||
|
$item['state_text'] = isset($this->stateMap[$item['state']]) ? $this->stateMap[$item['state']] : '未知';
|
||||||
|
$item['ctime_text'] = $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : '';
|
||||||
|
$item['ltime_text'] = $item['ltime'] ? date('Y-m-d H:i:s', $item['ltime']) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess([
|
||||||
|
'list' => $list,
|
||||||
|
'total' => $total,
|
||||||
|
'pageIndex' => $page,
|
||||||
|
'pageSize' => $pageSize,
|
||||||
|
'totalPages' => $total > 0 ? ceil($total / $pageSize) : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取专家详情(含所有领域)
|
||||||
|
*/
|
||||||
|
public function getDetail()
|
||||||
|
{
|
||||||
|
$expertId = intval($this->request->param('expert_id', 0));
|
||||||
|
if (!$expertId) {
|
||||||
|
return jsonError('expert_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expert = Db::name('expert')->where('expert_id', $expertId)->find();
|
||||||
|
if (!$expert) {
|
||||||
|
return jsonError('专家不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expert['fields'] = Db::name('expert_field')
|
||||||
|
->where('expert_id', $expertId)
|
||||||
|
->where('state', 0)
|
||||||
|
->select();
|
||||||
|
$expert['state_text'] = isset($this->stateMap[$expert['state']]) ? $this->stateMap[$expert['state']] : '未知';
|
||||||
|
$expert['ctime_text'] = $expert['ctime'] ? date('Y-m-d H:i:s', $expert['ctime']) : '';
|
||||||
|
$expert['ltime_text'] = $expert['ltime'] ? date('Y-m-d H:i:s', $expert['ltime']) : '';
|
||||||
|
|
||||||
|
return jsonSuccess($expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加专家
|
||||||
|
*/
|
||||||
|
public function addExpert()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$name = trim(isset($data['name']) ? $data['name'] : '');
|
||||||
|
$email = trim(isset($data['email']) ? $data['email'] : '');
|
||||||
|
|
||||||
|
if ($name === '' || $email === '') {
|
||||||
|
return jsonError('name和email不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Db::name('expert')->where('email', $email)->find();
|
||||||
|
if ($exists) {
|
||||||
|
return jsonError('该邮箱已存在,expert_id=' . $exists['expert_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$insert = [
|
||||||
|
'name' => $name,
|
||||||
|
'email' => $email,
|
||||||
|
'affiliation' => trim(isset($data['affiliation']) ? $data['affiliation'] : ''),
|
||||||
|
'source' => trim(isset($data['source']) ? $data['source'] : 'manual'),
|
||||||
|
'ctime' => time(),
|
||||||
|
'ltime' => 0,
|
||||||
|
'state' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$expertId = Db::name('expert')->insertGetId($insert);
|
||||||
|
|
||||||
|
if (!empty($data['fields'])) {
|
||||||
|
$this->saveExpertFields($expertId, $data['fields']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess(['expert_id' => $expertId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑专家
|
||||||
|
*/
|
||||||
|
public function editExpert()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$expertId = intval(isset($data['expert_id']) ? $data['expert_id'] : 0);
|
||||||
|
if (!$expertId) {
|
||||||
|
return jsonError('expert_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expert = Db::name('expert')->where('expert_id', $expertId)->find();
|
||||||
|
if (!$expert) {
|
||||||
|
return jsonError('专家不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$update = [];
|
||||||
|
if (isset($data['name'])) $update['name'] = trim($data['name']);
|
||||||
|
if (isset($data['email'])) $update['email'] = trim($data['email']);
|
||||||
|
if (isset($data['affiliation'])) $update['affiliation'] = trim($data['affiliation']);
|
||||||
|
if (isset($data['source'])) $update['source'] = trim($data['source']);
|
||||||
|
if (isset($data['state'])) $update['state'] = intval($data['state']);
|
||||||
|
|
||||||
|
if (!empty($update)) {
|
||||||
|
Db::name('expert')->where('expert_id', $expertId)->update($update);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['fields'])) {
|
||||||
|
Db::name('expert_field')->where('expert_id', $expertId)->where('state', 0)->update(['state' => 1]);
|
||||||
|
if (!empty($data['fields'])) {
|
||||||
|
$this->saveExpertFields($expertId, $data['fields']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess(['expert_id' => $expertId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量修改状态
|
||||||
|
*
|
||||||
|
* 参数:
|
||||||
|
* expert_ids - 逗号分隔的ID列表 "1,2,3"
|
||||||
|
* state - 目标状态 0-5
|
||||||
|
*/
|
||||||
|
public function updateState()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$expertIds = isset($data['expert_ids']) ? $data['expert_ids'] : '';
|
||||||
|
$state = intval(isset($data['state']) ? $data['state'] : -1);
|
||||||
|
|
||||||
|
if (empty($expertIds)) {
|
||||||
|
return jsonError('expert_ids is required');
|
||||||
|
}
|
||||||
|
if ($state < 0 || $state > 5) {
|
||||||
|
return jsonError('state取值范围0-5');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_map('intval', explode(',', $expertIds));
|
||||||
|
$count = Db::name('expert')->where('expert_id', 'in', $ids)->update(['state' => $state]);
|
||||||
|
|
||||||
|
return jsonSuccess(['updated' => $count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除专家(软删除,设为黑名单状态5)
|
||||||
|
*
|
||||||
|
* 参数:
|
||||||
|
* expert_ids - 逗号分隔的ID列表
|
||||||
|
* hard - 传1则物理删除
|
||||||
|
*/
|
||||||
|
public function deleteExpert()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$expertIds = isset($data['expert_ids']) ? $data['expert_ids'] : '';
|
||||||
|
$hard = intval(isset($data['hard']) ? $data['hard'] : 0);
|
||||||
|
|
||||||
|
if (empty($expertIds)) {
|
||||||
|
return jsonError('expert_ids is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_map('intval', explode(',', $expertIds));
|
||||||
|
|
||||||
|
if ($hard) {
|
||||||
|
Db::name('expert_field')->where('expert_id', 'in', $ids)->delete();
|
||||||
|
$count = Db::name('expert')->where('expert_id', 'in', $ids)->delete();
|
||||||
|
} else {
|
||||||
|
$count = Db::name('expert')->where('expert_id', 'in', $ids)->update(['state' => 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess(['affected' => $count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给专家添加领域
|
||||||
|
*/
|
||||||
|
public function addField()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$expertId = intval(isset($data['expert_id']) ? $data['expert_id'] : 0);
|
||||||
|
$majorId = intval(isset($data['major_id']) ? $data['major_id'] : 0);
|
||||||
|
$field = trim(isset($data['field']) ? $data['field'] : '');
|
||||||
|
|
||||||
|
if (!$expertId || $field === '') {
|
||||||
|
return jsonError('expert_id和field不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Db::name('expert_field')
|
||||||
|
->where('expert_id', $expertId)
|
||||||
|
->where('field', $field)
|
||||||
|
->where('state', 0)
|
||||||
|
->find();
|
||||||
|
if ($exists) {
|
||||||
|
return jsonError('该领域已存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = Db::name('expert_field')->insertGetId([
|
||||||
|
'expert_id' => $expertId,
|
||||||
|
'major_id' => $majorId,
|
||||||
|
'field' => $field,
|
||||||
|
'state' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return jsonSuccess(['expert_field_id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除领域(软删除)
|
||||||
|
*/
|
||||||
|
public function removeField()
|
||||||
|
{
|
||||||
|
$efId = intval($this->request->param('expert_field_id', 0));
|
||||||
|
if (!$efId) {
|
||||||
|
return jsonError('expert_field_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('expert_field')->where('expert_field_id', $efId)->update(['state' => 1]);
|
||||||
|
|
||||||
|
return jsonSuccess([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有不重复的领域列表(用于筛选下拉框)
|
||||||
|
*/
|
||||||
|
public function getFieldOptions()
|
||||||
|
{
|
||||||
|
$list = Db::name('expert_field')
|
||||||
|
->where('state', 0)
|
||||||
|
->group('field')
|
||||||
|
->column('field');
|
||||||
|
|
||||||
|
return jsonSuccess($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有来源列表(用于筛选下拉框)
|
||||||
|
*/
|
||||||
|
public function getSourceOptions()
|
||||||
|
{
|
||||||
|
$list = Db::name('expert')
|
||||||
|
->where('source', '<>', '')
|
||||||
|
->group('source')
|
||||||
|
->column('source');
|
||||||
|
|
||||||
|
return jsonSuccess($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出某个领域的专家为Excel
|
||||||
|
*
|
||||||
|
* 参数:
|
||||||
|
* field - 领域关键词(必填)
|
||||||
|
* major_id - 学科ID(可选)
|
||||||
|
* state - 状态筛选(可选,默认不过滤)
|
||||||
|
* keyword - 搜索姓名/邮箱/单位
|
||||||
|
* source - 来源筛选
|
||||||
|
*/
|
||||||
|
public function exportExcel()
|
||||||
|
{
|
||||||
|
$data = $this->request->param();
|
||||||
|
$field = trim(isset($data['field']) ? $data['field'] : '');
|
||||||
|
$state = isset($data['state']) ? $data['state'] : '-1';
|
||||||
|
$keyword = trim(isset($data['keyword']) ? $data['keyword'] : '');
|
||||||
|
$source = trim(isset($data['source']) ? $data['source'] : '');
|
||||||
|
|
||||||
|
$query = Db::name('expert')->alias('e');
|
||||||
|
|
||||||
|
if ($field !== '') {
|
||||||
|
$query->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner');
|
||||||
|
if ($field !== '') {
|
||||||
|
$query->where('ef.field', 'like', '%' . $field . '%');
|
||||||
|
}
|
||||||
|
$query->group('e.expert_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state !== '-1' && $state !== '') {
|
||||||
|
$query->where('e.state', intval($state));
|
||||||
|
}
|
||||||
|
if ($keyword !== '') {
|
||||||
|
$query->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%');
|
||||||
|
}
|
||||||
|
if ($source !== '') {
|
||||||
|
$query->where('e.source', $source);
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $query->field('e.*')->order('e.ctime desc')->select();
|
||||||
|
|
||||||
|
if (empty($list)) {
|
||||||
|
return jsonError('没有符合条件的数据可导出');
|
||||||
|
}
|
||||||
|
|
||||||
|
$expertIds = array_column($list, 'expert_id');
|
||||||
|
$allFields = Db::name('expert_field')
|
||||||
|
->where('expert_id', 'in', $expertIds)
|
||||||
|
->where('state', 0)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
$fieldMap = [];
|
||||||
|
foreach ($allFields as $f) {
|
||||||
|
$fieldMap[$f['expert_id']][] = $f['field'];
|
||||||
|
}
|
||||||
|
|
||||||
|
vendor("PHPExcel.PHPExcel");
|
||||||
|
|
||||||
|
$objPHPExcel = new \PHPExcel();
|
||||||
|
$sheet = $objPHPExcel->getActiveSheet();
|
||||||
|
$sheet->setTitle('Expert List');
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'A' => '#',
|
||||||
|
'B' => 'Name',
|
||||||
|
'C' => 'Email',
|
||||||
|
'D' => 'Affiliation',
|
||||||
|
'E' => 'Source',
|
||||||
|
'F' => 'Fields',
|
||||||
|
'G' => 'State',
|
||||||
|
'H' => 'Add Time',
|
||||||
|
'I' => 'Last Promotion',
|
||||||
|
];
|
||||||
|
foreach ($headers as $col => $header) {
|
||||||
|
$sheet->setCellValue($col . '1', $header);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerStyle = [
|
||||||
|
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
|
||||||
|
'fill' => ['type' => \PHPExcel_Style_Fill::FILL_SOLID, 'startcolor' => ['rgb' => '4472C4']],
|
||||||
|
'alignment' => ['horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER],
|
||||||
|
];
|
||||||
|
$sheet->getStyle('A1:I1')->applyFromArray($headerStyle);
|
||||||
|
|
||||||
|
foreach ($list as $i => $item) {
|
||||||
|
$row = $i + 2;
|
||||||
|
$fields = isset($fieldMap[$item['expert_id']]) ? implode(', ', $fieldMap[$item['expert_id']]) : '';
|
||||||
|
$stateText = isset($this->stateMap[$item['state']]) ? $this->stateMap[$item['state']] : '未知';
|
||||||
|
|
||||||
|
$sheet->setCellValue('A' . $row, $i + 1);
|
||||||
|
$sheet->setCellValue('B' . $row, $item['name']);
|
||||||
|
$sheet->setCellValueExplicit('C' . $row, $item['email'], \PHPExcel_Cell_DataType::TYPE_STRING);
|
||||||
|
$sheet->setCellValue('D' . $row, $item['affiliation']);
|
||||||
|
$sheet->setCellValue('E' . $row, $item['source']);
|
||||||
|
$sheet->setCellValue('F' . $row, $fields);
|
||||||
|
$sheet->setCellValue('G' . $row, $stateText);
|
||||||
|
$sheet->setCellValue('H' . $row, $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : '');
|
||||||
|
$sheet->setCellValue('I' . $row, $item['ltime'] ? date('Y-m-d H:i:s', $item['ltime']) : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->getColumnDimension('A')->setWidth(6);
|
||||||
|
$sheet->getColumnDimension('B')->setWidth(25);
|
||||||
|
$sheet->getColumnDimension('C')->setWidth(35);
|
||||||
|
$sheet->getColumnDimension('D')->setWidth(40);
|
||||||
|
$sheet->getColumnDimension('E')->setWidth(15);
|
||||||
|
$sheet->getColumnDimension('F')->setWidth(50);
|
||||||
|
$sheet->getColumnDimension('G')->setWidth(15);
|
||||||
|
$sheet->getColumnDimension('H')->setWidth(20);
|
||||||
|
$sheet->getColumnDimension('I')->setWidth(20);
|
||||||
|
|
||||||
|
$label = $field !== '' ? preg_replace('/[^a-zA-Z0-9_\x{4e00}-\x{9fa5}]/u', '_', $field) : 'all';
|
||||||
|
$filename = 'expert_' . $label . '_' . date('Ymd_His') . '.xlsx';
|
||||||
|
|
||||||
|
$dir = ROOT_PATH . 'public' . DS . 'exports';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filepath = $dir . DS . $filename;
|
||||||
|
$writer = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
|
||||||
|
$writer->save($filepath);
|
||||||
|
|
||||||
|
return jsonSuccess([
|
||||||
|
'file_url' => '/exports/' . $filename,
|
||||||
|
'file_name' => $filename,
|
||||||
|
'count' => count($list),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存专家领域
|
||||||
|
* @param int $expertId
|
||||||
|
* @param array $fields [{"major_id":1,"field":"xxx"}, ...]
|
||||||
|
*/
|
||||||
|
private function saveExpertFields($expertId, $fields)
|
||||||
|
{
|
||||||
|
if (is_string($fields)) {
|
||||||
|
$fields = json_decode($fields, true);
|
||||||
|
}
|
||||||
|
if (!is_array($fields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$majorId = intval(isset($f['major_id']) ? $f['major_id'] : 0);
|
||||||
|
$fieldName = trim(isset($f['field']) ? $f['field'] : '');
|
||||||
|
if ($fieldName === '') continue;
|
||||||
|
|
||||||
|
$exists = Db::name('expert_field')
|
||||||
|
->where('expert_id', $expertId)
|
||||||
|
->where('field', $fieldName)
|
||||||
|
->where('state', 0)
|
||||||
|
->find();
|
||||||
|
if ($exists) continue;
|
||||||
|
|
||||||
|
Db::name('expert_field')->insert([
|
||||||
|
'expert_id' => $expertId,
|
||||||
|
'major_id' => $majorId,
|
||||||
|
'field' => $fieldName,
|
||||||
|
'state' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Expert Fetch Field Management ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取抓取领域列表
|
||||||
|
* 参数: state(-1不过滤), keyword(搜索field), pageIndex, pageSize
|
||||||
|
*/
|
||||||
|
public function getFetchList()
|
||||||
|
{
|
||||||
|
$data = $this->request->param();
|
||||||
|
$state = isset($data['state']) ? $data['state'] : '-1';
|
||||||
|
$keyword = trim(isset($data['keyword']) ? $data['keyword'] : '');
|
||||||
|
$page = max(1, intval(isset($data['pageIndex']) ? $data['pageIndex'] : 1));
|
||||||
|
$pageSize = max(1, intval(isset($data['pageSize']) ? $data['pageSize'] : 20));
|
||||||
|
|
||||||
|
$query = Db::name('expert_fetch');
|
||||||
|
|
||||||
|
if ($state !== '-1' && $state !== '') {
|
||||||
|
$query->where('state', intval($state));
|
||||||
|
}
|
||||||
|
if ($keyword !== '') {
|
||||||
|
$query->where('field', 'like', '%' . $keyword . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$countQuery = Db::name('expert_fetch');
|
||||||
|
if ($state !== '-1' && $state !== '') {
|
||||||
|
$countQuery->where('state', intval($state));
|
||||||
|
}
|
||||||
|
if ($keyword !== '') {
|
||||||
|
$countQuery->where('field', 'like', '%' . $keyword . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $countQuery->count();
|
||||||
|
$list = $query->order('expert_fetch_id desc')->page($page, $pageSize)->select();
|
||||||
|
|
||||||
|
foreach ($list as &$item) {
|
||||||
|
$item['last_time_text'] = $item['last_time'] ? date('Y-m-d H:i:s', $item['last_time']) : '';
|
||||||
|
$item['ctime_text'] = $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : '';
|
||||||
|
$fieldName = trim($item['field']);
|
||||||
|
|
||||||
|
$item['journal_count'] = Db::name('journal_promotion_field')
|
||||||
|
->where('expert_fetch_id', $item['expert_fetch_id'])
|
||||||
|
->where('state', 0)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 与推广选人一致:按字段关键词匹配,统计可用专家(去重)
|
||||||
|
$item['expert_count'] = Db::name('expert_field')->alias('ef')
|
||||||
|
->join('t_expert e', 'e.expert_id = ef.expert_id', 'inner')
|
||||||
|
->where('ef.state', 0)
|
||||||
|
->where('e.state', 0)
|
||||||
|
->where('ef.field', 'like', '%' . $fieldName . '%')
|
||||||
|
->count('distinct ef.expert_id');
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess([
|
||||||
|
'list' => $list,
|
||||||
|
'total' => $total,
|
||||||
|
'pageIndex' => $page,
|
||||||
|
'pageSize' => $pageSize,
|
||||||
|
'totalPages' => $total > 0 ? ceil($total / $pageSize) : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增抓取领域
|
||||||
|
* 参数: field(必填), source(选填,默认pubmed)
|
||||||
|
*/
|
||||||
|
public function addFetchField()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$field = trim(isset($data['field']) ? $data['field'] : '');
|
||||||
|
$source = trim(isset($data['source']) ? $data['source'] : 'pubmed');
|
||||||
|
|
||||||
|
if ($field === '') {
|
||||||
|
return jsonError('field不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Db::name('expert_fetch')
|
||||||
|
->where('field', $field)
|
||||||
|
->where('source', $source)
|
||||||
|
->find();
|
||||||
|
if ($exists) {
|
||||||
|
if ($exists['state'] == 1) {
|
||||||
|
Db::name('expert_fetch')
|
||||||
|
->where('expert_fetch_id', $exists['expert_fetch_id'])
|
||||||
|
->update(['state' => 0]);
|
||||||
|
return jsonSuccess(['expert_fetch_id' => $exists['expert_fetch_id'], 'msg' => 'reactivated']);
|
||||||
|
}
|
||||||
|
return jsonError('该领域已存在 (expert_fetch_id=' . $exists['expert_fetch_id'] . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = Db::name('expert_fetch')->insertGetId([
|
||||||
|
'field' => mb_substr($field, 0, 128),
|
||||||
|
'source' => mb_substr($source, 0, 128),
|
||||||
|
'last_page' => 0,
|
||||||
|
'total_pages' => 0,
|
||||||
|
'last_time' => 0,
|
||||||
|
'state' => 0,
|
||||||
|
'ctime' => time(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return jsonSuccess(['expert_fetch_id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑抓取领域
|
||||||
|
* 参数: expert_fetch_id(必填), field, source, state
|
||||||
|
*/
|
||||||
|
public function editFetchField()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$id = intval(isset($data['expert_fetch_id']) ? $data['expert_fetch_id'] : 0);
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('expert_fetch_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = Db::name('expert_fetch')->where('expert_fetch_id', $id)->find();
|
||||||
|
if (!$record) {
|
||||||
|
return jsonError('记录不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$update = [];
|
||||||
|
if (isset($data['field'])) $update['field'] = mb_substr(trim($data['field']), 0, 128);
|
||||||
|
if (isset($data['source'])) $update['source'] = mb_substr(trim($data['source']), 0, 128);
|
||||||
|
if (isset($data['state'])) $update['state'] = intval($data['state']);
|
||||||
|
|
||||||
|
if (!empty($update)) {
|
||||||
|
Db::name('expert_fetch')->where('expert_fetch_id', $id)->update($update);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess(['expert_fetch_id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除/停用抓取领域(软删除 state=1)
|
||||||
|
* 参数: expert_fetch_id(必填), hard(传1物理删除)
|
||||||
|
*/
|
||||||
|
public function deleteFetchField()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$id = intval(isset($data['expert_fetch_id']) ? $data['expert_fetch_id'] : 0);
|
||||||
|
$hard = intval(isset($data['hard']) ? $data['hard'] : 0);
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('expert_fetch_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hard) {
|
||||||
|
Db::name('journal_promotion_field')->where('expert_fetch_id', $id)->delete();
|
||||||
|
Db::name('expert_fetch')->where('expert_fetch_id', $id)->delete();
|
||||||
|
} else {
|
||||||
|
Db::name('expert_fetch')->where('expert_fetch_id', $id)->update(['state' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -334,6 +334,14 @@ class Journal extends Base {
|
|||||||
$aJournalUpdate['wechat_app_secret'] = $update['wechat_app_secret'];
|
$aJournalUpdate['wechat_app_secret'] = $update['wechat_app_secret'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isset($data['editor_name'])&&$data['editor_name']!=''){
|
||||||
|
$update['editor_name'] = $data['editor_name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($data['databases'])&&$data['databases']!=''){
|
||||||
|
$update['databases'] = $data['databases'];
|
||||||
|
}
|
||||||
|
|
||||||
if(!empty($aJournalUpdate)){
|
if(!empty($aJournalUpdate)){
|
||||||
$aJournalUpdate['issn'] = $journal_info['issn'];
|
$aJournalUpdate['issn'] = $journal_info['issn'];
|
||||||
$sUrl = $this->sJournalUrl."wechat/Article/updateJournal";
|
$sUrl = $this->sJournalUrl."wechat/Article/updateJournal";
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ class MailTemplate extends Base
|
|||||||
return jsonSuccess(['list' => $list]);
|
return jsonSuccess(['list' => $list]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function listTemplatesAll(){
|
||||||
|
$list = Db::name('mail_template')
|
||||||
|
->where('state', 0)
|
||||||
|
->order('is_active desc, utime desc, template_id desc')
|
||||||
|
->select();
|
||||||
|
return jsonSuccess(['list'=>$list]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or update a global mail style
|
* Create or update a global mail style
|
||||||
* 当前 style 表字段:
|
* 当前 style 表字段:
|
||||||
|
|||||||
@@ -843,14 +843,14 @@ class Preaccept extends Base
|
|||||||
}
|
}
|
||||||
$am_info = $this->article_main_obj->where("am_id",$data['am_id'])->find();
|
$am_info = $this->article_main_obj->where("am_id",$data['am_id'])->find();
|
||||||
//上一行,空行
|
//上一行,空行
|
||||||
$p_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort","<",$am_info['sort'])->whereIn("state",[0,2])->order("sort desc")->limit(1)->select();
|
// $p_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort","<",$am_info['sort'])->whereIn("state",[0,2])->order("sort desc")->limit(1)->select();
|
||||||
if($p_list&&($p_list[0]['type']>0||$p_list[0]['content']!="")){
|
// if($p_list&&($p_list[0]['type']>0||$p_list[0]['content']!="")){
|
||||||
$this->addBRow($am_info['article_id'],$p_list[0]['am_id']);
|
// $this->addBRow($am_info['article_id'],$p_list[0]['am_id']);
|
||||||
}
|
// }
|
||||||
$n_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort",">",$am_info['sort'])->whereIn("state",[0,2])->order("sort asc")->limit(1)->select();
|
// $n_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort",">",$am_info['sort'])->whereIn("state",[0,2])->order("sort asc")->limit(1)->select();
|
||||||
if($n_list[0]['type']>0||$n_list[0]['content']!=""){
|
// if($n_list[0]['type']>0||$n_list[0]['content']!=""){
|
||||||
$this->addBRow($am_info['article_id'],$data['am_id']);
|
// $this->addBRow($am_info['article_id'],$data['am_id']);
|
||||||
}
|
// }
|
||||||
$this->article_main_obj->where("am_id",$data['am_id'])->update(["is_h1"=>1,"is_h2"=>0,"is_h3"=>0]);
|
$this->article_main_obj->where("am_id",$data['am_id'])->update(["is_h1"=>1,"is_h2"=>0,"is_h3"=>0]);
|
||||||
// return jsonSuccess([]);
|
// return jsonSuccess([]);
|
||||||
//返回数据 20260119 start
|
//返回数据 20260119 start
|
||||||
@@ -872,10 +872,10 @@ class Preaccept extends Base
|
|||||||
}
|
}
|
||||||
$am_info = $this->article_main_obj->where("am_id",$data['am_id'])->find();
|
$am_info = $this->article_main_obj->where("am_id",$data['am_id'])->find();
|
||||||
//上一行,空行
|
//上一行,空行
|
||||||
$p_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort","<",$am_info['sort'])->whereIn("state",[0,2])->order("sort desc")->limit(1)->select();
|
// $p_list = $this->article_main_obj->where("article_id",$am_info['article_id'])->where("sort","<",$am_info['sort'])->whereIn("state",[0,2])->order("sort desc")->limit(1)->select();
|
||||||
if($p_list&&($p_list[0]['type']>0||$p_list[0]['content']!="")){
|
// if($p_list&&($p_list[0]['type']>0||$p_list[0]['content']!="")){
|
||||||
$this->addBRow($am_info['article_id'],$p_list[0]['am_id']);
|
// $this->addBRow($am_info['article_id'],$p_list[0]['am_id']);
|
||||||
}
|
// }
|
||||||
$this->article_main_obj->where("am_id",$data['am_id'])->update(["is_h1"=>0,"is_h2"=>1,"is_h3"=>0]);
|
$this->article_main_obj->where("am_id",$data['am_id'])->update(["is_h1"=>0,"is_h2"=>1,"is_h3"=>0]);
|
||||||
// return jsonSuccess([]);
|
// return jsonSuccess([]);
|
||||||
//返回数据 20260119 start
|
//返回数据 20260119 start
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ class PromotionSend
|
|||||||
$service = new PromotionService();
|
$service = new PromotionService();
|
||||||
|
|
||||||
if (!$taskId) {
|
if (!$taskId) {
|
||||||
$service->log('[PromotionSend] missing task_id, job deleted');
|
// $service->log('[PromotionSend] missing task_id, job deleted');
|
||||||
$job->delete();
|
$job->delete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
$result = $service->processNextEmail($taskId);
|
$result = $service->processNextEmail($taskId);
|
||||||
$service->log('[PromotionSend] task=' . $taskId . ' result=' . json_encode($result));
|
// $service->log('[PromotionSend] task=' . $taskId . ' result=' . json_encode($result));
|
||||||
|
|
||||||
if (!empty($result['done'])) {
|
// if (!empty($result['done'])) {
|
||||||
$reason = isset($result['reason']) ? $result['reason'] : '';
|
// $reason = isset($result['reason']) ? $result['reason'] : '';
|
||||||
$service->log('[PromotionSend] task=' . $taskId . ' finished, reason=' . $reason);
|
// $service->log('[PromotionSend] task=' . $taskId . ' finished, reason=' . $reason);
|
||||||
}
|
// }
|
||||||
} catch (\Exception $e) {
|
// } catch (\Exception $e) {
|
||||||
$service->log('[PromotionSend] task=' . $taskId . ' exception=' . $e->getMessage());
|
// $service->log('[PromotionSend] task=' . $taskId . ' exception=' . $e->getMessage());
|
||||||
}
|
// }
|
||||||
|
|
||||||
$job->delete();
|
$job->delete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,15 @@ class ExpertFinderService
|
|||||||
$result = $this->searchViaPubMed($field, $perPage, $minYear, $page);
|
$result = $this->searchViaPubMed($field, $perPage, $minYear, $page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!isset($result['total'])){
|
||||||
|
return [
|
||||||
|
"has_more"=>"no"
|
||||||
|
];
|
||||||
|
}
|
||||||
$saveResult = $this->saveExperts($result['experts'], $field, $source);
|
$saveResult = $this->saveExperts($result['experts'], $field, $source);
|
||||||
|
|
||||||
$nextPage = $result['has_more'] ? $page : 0;
|
$nextPage = $result['has_more'] ? $page : $fetchLog['last_page'];
|
||||||
$totalPages = isset($result['total_pages']) ? $result['total_pages'] : 0;
|
$totalPages = $result['total_pages'] ?? $fetchLog['total_pages'];
|
||||||
$this->updateFetchLog($field, $source, $nextPage, $totalPages);
|
$this->updateFetchLog($field, $source, $nextPage, $totalPages);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -48,6 +53,7 @@ class ExpertFinderService
|
|||||||
'experts_found' => $result['total'],
|
'experts_found' => $result['total'],
|
||||||
'saved_new' => $saveResult['inserted'],
|
'saved_new' => $saveResult['inserted'],
|
||||||
'saved_exist' => $saveResult['existing'],
|
'saved_exist' => $saveResult['existing'],
|
||||||
|
'list' => $result['experts'],
|
||||||
'field_enriched' => $saveResult['field_enriched'],
|
'field_enriched' => $saveResult['field_enriched'],
|
||||||
'has_more' => $result['has_more'],
|
'has_more' => $result['has_more'],
|
||||||
];
|
];
|
||||||
@@ -68,6 +74,8 @@ class ExpertFinderService
|
|||||||
$fieldEnrich = 0;
|
$fieldEnrich = 0;
|
||||||
|
|
||||||
foreach ($experts as $expert) {
|
foreach ($experts as $expert) {
|
||||||
|
|
||||||
|
|
||||||
$email = strtolower(trim($expert['email']));
|
$email = strtolower(trim($expert['email']));
|
||||||
if (empty($email)) {
|
if (empty($email)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -94,6 +102,9 @@ class ExpertFinderService
|
|||||||
try {
|
try {
|
||||||
$expertId = Db::name('expert')->insertGetId($insert);
|
$expertId = Db::name('expert')->insertGetId($insert);
|
||||||
$this->enrichExpertField($expertId, $field);
|
$this->enrichExpertField($expertId, $field);
|
||||||
|
if(isset($expert['papers'])&&is_array($expert['papers'])){
|
||||||
|
$this->savePaper($expertId, $expert['papers']);
|
||||||
|
}
|
||||||
$inserted++;
|
$inserted++;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$existing++;
|
$existing++;
|
||||||
@@ -103,6 +114,25 @@ class ExpertFinderService
|
|||||||
return ['inserted' => $inserted, 'existing' => $existing, 'field_enriched' => $fieldEnrich];
|
return ['inserted' => $inserted, 'existing' => $existing, 'field_enriched' => $fieldEnrich];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function savePaper($expertId, $papers)
|
||||||
|
{
|
||||||
|
foreach ($papers as $paper){
|
||||||
|
$check = Db::name('expert_paper')->where("expert_id",$expertId)->where('paper_article_id',$paper['article_id'])->find();
|
||||||
|
if($check){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$insert = [
|
||||||
|
'expert_id' => $expertId,
|
||||||
|
'paper_title' => isset($paper['title'])?mb_substr($paper['title'], 0, 255):"",
|
||||||
|
'paper_article_id' => $paper['article_id'] ?? 0,
|
||||||
|
'paper_journal' => isset($paper['journal'])?mb_substr($paper['journal'], 0, 128):"",
|
||||||
|
'ctime' => time(),
|
||||||
|
];
|
||||||
|
Db::name('expert_paper')->insert($insert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getFetchLog($field, $source)
|
public function getFetchLog($field, $source)
|
||||||
{
|
{
|
||||||
$log = Db::name('expert_fetch')
|
$log = Db::name('expert_fetch')
|
||||||
@@ -519,10 +549,8 @@ class ExpertFinderService
|
|||||||
->where('state', 0)
|
->where('state', 0)
|
||||||
->find();
|
->find();
|
||||||
if ($exists) return 0;
|
if ($exists) return 0;
|
||||||
|
|
||||||
Db::name('expert_field')->insert([
|
Db::name('expert_field')->insert([
|
||||||
'expert_id' => $expertId,
|
'expert_id' => $expertId,
|
||||||
'major_id' => 0,
|
|
||||||
'field' => mb_substr($field, 0, 128),
|
'field' => mb_substr($field, 0, 128),
|
||||||
'state' => 0,
|
'state' => 0,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -80,33 +80,43 @@ class PromotionService
|
|||||||
return ['done' => false, 'reason' => 'no_smtp_quota', 'retry_in' => 600];
|
return ['done' => false, 'reason' => 'no_smtp_quota', 'retry_in' => 600];
|
||||||
}
|
}
|
||||||
|
|
||||||
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
// 优先使用预生成内容;无则现场渲染
|
||||||
$expertVars = $this->buildExpertVars($expert);
|
$subject = '';
|
||||||
$journalVars = $this->buildJournalVars($journal);
|
$body = '';
|
||||||
$vars = array_merge($journalVars, $expertVars);
|
$hasPrepared = !empty($logEntry['subject_prepared']) && !empty($logEntry['body_prepared']);
|
||||||
|
|
||||||
$rendered = $this->renderFromTemplate(
|
if ($hasPrepared) {
|
||||||
$task['template_id'],
|
$subject = $logEntry['subject_prepared'];
|
||||||
$task['journal_id'],
|
$body = $logEntry['body_prepared'];
|
||||||
json_encode($vars, JSON_UNESCAPED_UNICODE),
|
} else {
|
||||||
$task['style_id']
|
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
||||||
);
|
$expertVars = $this->buildExpertVars($expert);
|
||||||
|
$journalVars = $this->buildJournalVars($journal);
|
||||||
|
$vars = array_merge($journalVars, $expertVars);
|
||||||
|
|
||||||
if ($rendered['code'] !== 0) {
|
$rendered = $this->renderFromTemplate(
|
||||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
$task['template_id'],
|
||||||
'state' => 2,
|
$task['journal_id'],
|
||||||
'error_msg' => 'Template render failed: ' . $rendered['msg'],
|
json_encode($vars, JSON_UNESCAPED_UNICODE),
|
||||||
'send_time' => time(),
|
$task['style_id']
|
||||||
]);
|
);
|
||||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('fail_count');
|
|
||||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
if ($rendered['code'] !== 0) {
|
||||||
$this->enqueueNextEmail($taskId, 2);
|
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||||
return ['done' => false, 'failed' => $logEntry['email_to'], 'reason' => 'template_error'];
|
'state' => 2,
|
||||||
|
'error_msg' => 'Template render failed: ' . $rendered['msg'],
|
||||||
|
'send_time' => time(),
|
||||||
|
]);
|
||||||
|
Db::name('promotion_task')->where('task_id', $taskId)->setInc('fail_count');
|
||||||
|
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||||
|
$this->enqueueNextEmail($taskId, 2);
|
||||||
|
return ['done' => false, 'failed' => $logEntry['email_to'], 'reason' => 'template_error'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$subject = $rendered['data']['subject'];
|
||||||
|
$body = $rendered['data']['body'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$subject = $rendered['data']['subject'];
|
|
||||||
$body = $rendered['data']['body'];
|
|
||||||
|
|
||||||
$result = $this->doSendEmail($account, $logEntry['email_to'], $subject, $body);
|
$result = $this->doSendEmail($account, $logEntry['email_to'], $subject, $body);
|
||||||
|
|
||||||
$now = time();
|
$now = time();
|
||||||
@@ -118,7 +128,7 @@ class PromotionService
|
|||||||
'send_time' => $now,
|
'send_time' => $now,
|
||||||
]);
|
]);
|
||||||
Db::name('journal_email')->where('j_email_id', $account['j_email_id'])->setInc('today_sent');
|
Db::name('journal_email')->where('j_email_id', $account['j_email_id'])->setInc('today_sent');
|
||||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['ltime' => $now]);
|
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['state' => 1, 'ltime' => $now]);
|
||||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('sent_count');
|
Db::name('promotion_task')->where('task_id', $taskId)->setInc('sent_count');
|
||||||
} else {
|
} else {
|
||||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||||
@@ -144,6 +154,165 @@ class PromotionService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 准备与触发(今日准备、明日发送) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定任务预生成所有待发邮件的 subject/body,写入 log;完成后将任务置为 state=5(已准备)
|
||||||
|
* @param int $taskId
|
||||||
|
* @return array ['prepared' => int, 'failed' => int, 'error' => string|null]
|
||||||
|
*/
|
||||||
|
public function prepareTask($taskId)
|
||||||
|
{
|
||||||
|
$task = Db::name('promotion_task')->where('task_id', $taskId)->find();
|
||||||
|
if (!$task) {
|
||||||
|
return ['prepared' => 0, 'failed' => 0, 'error' => 'task_not_found'];
|
||||||
|
}
|
||||||
|
if ($task['state'] != 0) {
|
||||||
|
return ['prepared' => 0, 'failed' => 0, 'error' => 'task_state_not_draft'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
||||||
|
$logs = Db::name('promotion_email_log')
|
||||||
|
->where('task_id', $taskId)
|
||||||
|
->where('state', 0)
|
||||||
|
->where('prepared_at', 0)
|
||||||
|
->order('log_id asc')
|
||||||
|
->select();
|
||||||
|
|
||||||
|
$prepared = 0;
|
||||||
|
$failed = 0;
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
$journalVars = $this->buildJournalVars($journal);
|
||||||
|
foreach ($logs as $log) {
|
||||||
|
$expert = Db::name('expert')->where('expert_id', $log['expert_id'])->find();
|
||||||
|
if (!$expert) {
|
||||||
|
Db::name('promotion_email_log')->where('log_id', $log['log_id'])->update([
|
||||||
|
'state' => 2,
|
||||||
|
'error_msg' => 'Expert not found',
|
||||||
|
'send_time' => $now,
|
||||||
|
]);
|
||||||
|
$failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expertVars = $this->buildExpertVars($expert);
|
||||||
|
$vars = array_merge($journalVars, $expertVars);
|
||||||
|
$rendered = $this->renderFromTemplate(
|
||||||
|
$task['template_id'],
|
||||||
|
$task['journal_id'],
|
||||||
|
json_encode($vars, JSON_UNESCAPED_UNICODE),
|
||||||
|
$task['style_id']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($rendered['code'] !== 0) {
|
||||||
|
Db::name('promotion_email_log')->where('log_id', $log['log_id'])->update([
|
||||||
|
'state' => 2,
|
||||||
|
'error_msg' => 'Prepare failed: ' . $rendered['msg'],
|
||||||
|
'send_time' => $now,
|
||||||
|
]);
|
||||||
|
$failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('promotion_email_log')->where('log_id', $log['log_id'])->update([
|
||||||
|
'subject_prepared' => mb_substr($rendered['data']['subject'], 0, 512),
|
||||||
|
'body_prepared' => $rendered['data']['body'],
|
||||||
|
'prepared_at' => $now,
|
||||||
|
]);
|
||||||
|
$prepared++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('promotion_task')->where('task_id', $taskId)->update([
|
||||||
|
'state' => 5,
|
||||||
|
'utime' => $now,
|
||||||
|
]);
|
||||||
|
$this->log("prepareTask task_id={$taskId} prepared={$prepared} failed={$failed}");
|
||||||
|
|
||||||
|
return ['prepared' => $prepared, 'failed' => $failed, 'error' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定日期的任务批量预生成邮件(供定时任务调用,如每天 22:00 准备明天的)
|
||||||
|
* @param string $date Y-m-d,如 2026-03-12
|
||||||
|
* @return array ['tasks' => int, 'prepared' => int, 'failed' => int, 'details' => []]
|
||||||
|
*/
|
||||||
|
public function prepareTasksForDate($date)
|
||||||
|
{
|
||||||
|
$tasks = Db::name('promotion_task')
|
||||||
|
->where('send_date', $date)
|
||||||
|
->where('state', 0)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
$totalPrepared = 0;
|
||||||
|
$totalFailed = 0;
|
||||||
|
$details = [];
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
$ret = $this->prepareTask($task['task_id']);
|
||||||
|
$totalPrepared += $ret['prepared'];
|
||||||
|
$totalFailed += $ret['failed'];
|
||||||
|
$details[] = [
|
||||||
|
'task_id' => $task['task_id'],
|
||||||
|
'task_name' => $task['task_name'],
|
||||||
|
'prepared' => $ret['prepared'],
|
||||||
|
'failed' => $ret['failed'],
|
||||||
|
'error' => $ret['error'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log("prepareTasksForDate date={$date} tasks=" . count($tasks) . " prepared={$totalPrepared} failed={$totalFailed}");
|
||||||
|
return [
|
||||||
|
'tasks' => count($tasks),
|
||||||
|
'prepared' => $totalPrepared,
|
||||||
|
'failed' => $totalFailed,
|
||||||
|
'details' => $details,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发指定日期的已准备任务开始发送(供定时任务调用,如每天 8:00 触发今天的)
|
||||||
|
* 会先对 send_date=date 且 state=0 的任务做一次补准备,再启动所有 state=5 的任务
|
||||||
|
* @param string $date Y-m-d
|
||||||
|
* @return array ['prepared' => int, 'started' => int, 'task_ids' => []]
|
||||||
|
*/
|
||||||
|
public function startTasksForDate($date)
|
||||||
|
{
|
||||||
|
// 补准备:当天日期但尚未准备的任务(如 22:00 后创建)
|
||||||
|
$catchUpTasks = Db::name('promotion_task')
|
||||||
|
->where('send_date', $date)
|
||||||
|
->where('state', 0)
|
||||||
|
->select();
|
||||||
|
foreach ($catchUpTasks as $t) {
|
||||||
|
$this->prepareTask($t['task_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tasks = Db::name('promotion_task')
|
||||||
|
->where('send_date', $date)
|
||||||
|
->where('state', 5)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
$started = 0;
|
||||||
|
$taskIds = [];
|
||||||
|
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
Db::name('promotion_task')->where('task_id', $task['task_id'])->update([
|
||||||
|
'state' => 1,
|
||||||
|
'utime' => time(),
|
||||||
|
]);
|
||||||
|
$this->enqueueNextEmail($task['task_id'], 0);
|
||||||
|
$started++;
|
||||||
|
$taskIds[] = $task['task_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log("startTasksForDate date={$date} started={$started} task_ids=" . implode(',', $taskIds));
|
||||||
|
return [
|
||||||
|
'prepared' => count($catchUpTasks),
|
||||||
|
'started' => $started,
|
||||||
|
'task_ids' => $taskIds,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Queue ====================
|
// ==================== Queue ====================
|
||||||
|
|
||||||
public function enqueueNextEmail($taskId, $delay = 0)
|
public function enqueueNextEmail($taskId, $delay = 0)
|
||||||
@@ -270,8 +439,8 @@ class PromotionService
|
|||||||
if ($styleId) {
|
if ($styleId) {
|
||||||
$style = Db::name('mail_style')->where('style_id', $styleId)->where('state', 0)->find();
|
$style = Db::name('mail_style')->where('style_id', $styleId)->where('state', 0)->find();
|
||||||
if ($style) {
|
if ($style) {
|
||||||
$header = $style['header_html'] ?? '';
|
$header = $style['header_html'] ? $this->renderVars($style['header_html'],$vars):'';
|
||||||
$footer = $style['footer_html'] ?? '';
|
$footer = $style['footer_html'] ? $this->renderVars($style['footer_html'],$vars): '';
|
||||||
$finalBody = $header . $body . $footer;
|
$finalBody = $header . $body . $footer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,20 +451,33 @@ class PromotionService
|
|||||||
public function buildExpertVars($expert)
|
public function buildExpertVars($expert)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => $expert['name'] ?? '',
|
'expert_title' => "Ph.D",
|
||||||
'email' => $expert['email'] ?? '',
|
'expert_name' => $expert['name'] ?? '',
|
||||||
'affiliation' => $expert['affiliation'] ?? '',
|
'expert_email' => $expert['email'] ?? '',
|
||||||
'field' => $expert['field'] ?? '',
|
'expert_affiliation' => $expert['affiliation'] ?? '',
|
||||||
|
'expert_field' => $expert['field'] ?? '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildJournalVars($journal)
|
public function buildJournalVars($journal)
|
||||||
{
|
{
|
||||||
if (!$journal) return [];
|
if (!$journal) return [];
|
||||||
|
$zb = Db::name("board_to_journal")
|
||||||
|
->where("journal_id",$journal['journal_id'])
|
||||||
|
->where("state",0)
|
||||||
|
->where('type',0)
|
||||||
|
->find();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'journal_title' => $journal['title'] ?? '',
|
'journal_name' => $journal['title'] ?? '',
|
||||||
'journal_abbr' => $journal['jabbr'] ?? '',
|
'journal_abbr' => $journal['jabbr'] ?? '',
|
||||||
'journal_url' => $journal['website'] ?? '',
|
'journal_url' => $journal['website'] ?? '',
|
||||||
|
'journal_email' => $journal['email'] ?? '',
|
||||||
|
'indexing_databases' => $journal['databases'] ?? '',
|
||||||
|
'submission_url' => "https://submission.tmrjournals.com/",
|
||||||
|
'eic_name' => $zb['realname'] ?? '',
|
||||||
|
'editor_name' => $journal['editor_name'],
|
||||||
|
'special_support_deadline'=>date("Y-m-d",strtotime("+30 days"))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user