feat: Add SHZ log inspection and HighGo database querying capabilities
- Introduced `inspect-shz-logs` skill for inspecting local and production logs with bounded read-only commands. - Added `view.sh` script for log viewing with options for following logs and filtering by keywords. - Created `query-shz-highgo` skill for querying the SHZ HighGo database through an SSH gateway. - Implemented read-only SQL execution with strict controls on allowed statements and query structure. - Added new domain and mapper classes for managing outdoor fair job associations. - Enhanced `OutdoorFairController` to support job listing and management for outdoor fairs. - Created SQL scripts for initializing outdoor fair job data and managing menu visibility.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package com.ruoyi.cms.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* 户外招聘会岗位关联表自动迁移。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OutdoorFairJobMigration
|
||||
{
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void migrate()
|
||||
{
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
ResultSet tables = connection.getMetaData().getTables(
|
||||
connection.getCatalog(), "shz", "cms_outdoor_fair_job", new String[]{"TABLE"}))
|
||||
{
|
||||
if (tables.next())
|
||||
{
|
||||
log.info("户外招聘会岗位关联表已存在");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("检查户外招聘会岗位关联表失败: {}", e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement())
|
||||
{
|
||||
statement.execute("CREATE SEQUENCE shz.cms_outdoor_fair_job_id_seq " +
|
||||
"START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1");
|
||||
statement.execute("CREATE TABLE shz.cms_outdoor_fair_job (" +
|
||||
"id BIGINT NOT NULL DEFAULT nextval('shz.cms_outdoor_fair_job_id_seq') PRIMARY KEY, " +
|
||||
"fair_id BIGINT NOT NULL, company_id BIGINT NOT NULL, " +
|
||||
"job_id BIGINT NOT NULL, create_by VARCHAR(64), create_time TIMESTAMP, " +
|
||||
"update_by VARCHAR(64), update_time TIMESTAMP, remark VARCHAR(500), " +
|
||||
"del_flag CHAR(1) DEFAULT '0')");
|
||||
statement.execute("CREATE UNIQUE INDEX uk_cms_outdoor_fair_job " +
|
||||
"ON shz.cms_outdoor_fair_job (fair_id, job_id)");
|
||||
statement.execute("CREATE INDEX idx_cms_outdoor_fair_job_company " +
|
||||
"ON shz.cms_outdoor_fair_job (fair_id, company_id)");
|
||||
log.info("户外招聘会岗位关联表检查完成");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("户外招聘会岗位关联表迁移失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,19 @@ import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.cms.domain.FairCompany;
|
||||
import com.ruoyi.cms.domain.Job;
|
||||
import com.ruoyi.cms.domain.OutdoorFair;
|
||||
import com.ruoyi.cms.domain.OutdoorFairBooth;
|
||||
import com.ruoyi.cms.domain.OutdoorFairBoothBooking;
|
||||
import com.ruoyi.cms.domain.OutdoorFairDictDataRequest;
|
||||
import com.ruoyi.cms.domain.OutdoorFairJob;
|
||||
import com.ruoyi.cms.domain.VenueInfo;
|
||||
import com.ruoyi.cms.mapper.FairCompanyMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairBoothBookingMapper;
|
||||
import com.ruoyi.cms.mapper.OutdoorFairJobMapper;
|
||||
import com.ruoyi.cms.mapper.CompanyMapper;
|
||||
import com.ruoyi.cms.service.IJobService;
|
||||
import com.ruoyi.cms.service.IOutdoorFairService;
|
||||
import com.ruoyi.cms.service.IVenueInfoService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
@@ -72,6 +76,12 @@ public class OutdoorFairController extends BaseController
|
||||
@Autowired
|
||||
private OutdoorFairBoothMapper outdoorFairBoothMapper;
|
||||
|
||||
@Autowired
|
||||
private OutdoorFairJobMapper outdoorFairJobMapper;
|
||||
|
||||
@Autowired
|
||||
private IJobService jobService;
|
||||
|
||||
/**
|
||||
* 查询户外招聘会列表
|
||||
*/
|
||||
@@ -161,7 +171,8 @@ public class OutdoorFairController extends BaseController
|
||||
@ApiOperation("查询户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/companies")
|
||||
public AjaxResult listCompanies(@PathVariable Long fairId, String companyName, Integer current, Integer pageSize)
|
||||
public AjaxResult listCompanies(@PathVariable Long fairId, String companyName,
|
||||
Integer current, Integer pageSize)
|
||||
{
|
||||
List<FairCompany> relations = fairCompanyMapper.selectList(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
@@ -182,6 +193,11 @@ public class OutdoorFairController extends BaseController
|
||||
.stream()
|
||||
.filter(item -> item.getCompanyId() != null)
|
||||
.collect(Collectors.toMap(OutdoorFairBoothBooking::getCompanyId, item -> item, (a, b) -> a));
|
||||
Map<Long, Long> jobCountMap = outdoorFairJobMapper.countJobsByCompany(fairId).stream()
|
||||
.collect(Collectors.toMap(
|
||||
item -> Long.valueOf(String.valueOf(item.get("companyId"))),
|
||||
item -> Long.valueOf(String.valueOf(item.get("jobCount"))),
|
||||
(a, b) -> a));
|
||||
|
||||
List<Map<String, Object>> rows = new ArrayList<>();
|
||||
for (FairCompany relation : relations) {
|
||||
@@ -204,7 +220,7 @@ public class OutdoorFairController extends BaseController
|
||||
row.put("contactPhone", company.getContactPersonPhone());
|
||||
row.put("boothId", booking == null ? null : booking.getBoothId());
|
||||
row.put("boothNumber", booking == null ? null : booking.getBoothNumber());
|
||||
row.put("jobList", new ArrayList<>());
|
||||
row.put("jobCount", jobCountMap.getOrDefault(company.getCompanyId(), 0L));
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
@@ -219,6 +235,35 @@ public class OutdoorFairController extends BaseController
|
||||
return result;
|
||||
}
|
||||
|
||||
@ApiOperation("分页查询户外招聘会参会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/jobs")
|
||||
public AjaxResult listJobs(@PathVariable Long fairId, Long companyId, String companyName,
|
||||
String jobTitle, String education, String experience,
|
||||
Integer current, Integer pageSize)
|
||||
{
|
||||
int page = current == null || current < 1 ? 1 : current;
|
||||
int size = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 100);
|
||||
int offset = (page - 1) * size;
|
||||
List<Job> rows = outdoorFairJobMapper.selectFairJobPage(
|
||||
fairId, companyId, companyName, jobTitle, education, experience, offset, size);
|
||||
Long total = outdoorFairJobMapper.countFairJobs(
|
||||
fairId, companyId, companyName, jobTitle, education, experience);
|
||||
AjaxResult result = AjaxResult.success();
|
||||
result.put("rows", rows);
|
||||
result.put("total", total == null ? 0 : total);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ApiOperation("获取户外招聘会岗位详情")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:query')")
|
||||
@GetMapping("/{fairId}/jobs/{jobId}")
|
||||
public AjaxResult getCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
return success(jobService.selectJobByJobId(jobId));
|
||||
}
|
||||
|
||||
@ApiOperation("新增户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@PostMapping("/{fairId}/companies")
|
||||
@@ -236,11 +281,90 @@ public class OutdoorFairController extends BaseController
|
||||
@ApiOperation("移除户外招聘会参会企业")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@DeleteMapping("/{fairId}/companies/{id}")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AjaxResult removeCompany(@PathVariable Long fairId, @PathVariable Long id)
|
||||
{
|
||||
return toAjax(fairCompanyMapper.delete(new LambdaQueryWrapper<FairCompany>()
|
||||
FairCompany relation = fairCompanyMapper.selectById(id);
|
||||
if (relation == null || !fairId.equals(relation.getJobFairId())) {
|
||||
return AjaxResult.error("参会企业不存在");
|
||||
}
|
||||
outdoorFairJobMapper.delete(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getCompanyId, relation.getCompanyId()));
|
||||
return toAjax(fairCompanyMapper.deleteById(id));
|
||||
}
|
||||
|
||||
@ApiOperation("新增户外招聘会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@PostMapping("/{fairId}/companies/{companyId}/jobs")
|
||||
public AjaxResult addCompanyJob(@PathVariable Long fairId, @PathVariable Long companyId,
|
||||
@RequestBody Job job)
|
||||
{
|
||||
ensureFairCompanyExists(fairId, companyId);
|
||||
Company company = companyMapper.selectById(companyId);
|
||||
job.setCompanyId(companyId);
|
||||
job.setCompanyName(company.getName());
|
||||
if (job.getIsPublish() == null) {
|
||||
job.setIsPublish(1);
|
||||
}
|
||||
int inserted = jobService.insertJob(job);
|
||||
if (inserted <= 0) {
|
||||
return AjaxResult.error("岗位新增失败");
|
||||
}
|
||||
OutdoorFairJob relation = new OutdoorFairJob();
|
||||
relation.setFairId(fairId);
|
||||
relation.setCompanyId(companyId);
|
||||
relation.setJobId(job.getJobId());
|
||||
relation.setCreateBy(getUsername());
|
||||
outdoorFairJobMapper.insert(relation);
|
||||
return success(job);
|
||||
}
|
||||
|
||||
@ApiOperation("修改户外招聘会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@PutMapping("/{fairId}/jobs/{jobId}")
|
||||
public AjaxResult updateCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId,
|
||||
@RequestBody Job job)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
job.setJobId(jobId);
|
||||
// 当前弹窗不修改发布状态,避免重复触发岗位发布流程。
|
||||
job.setIsPublish(null);
|
||||
return toAjax(jobService.updateJob(job));
|
||||
}
|
||||
|
||||
@ApiOperation("删除户外招聘会岗位")
|
||||
@PreAuthorize("@ss.hasPermi('cms:outdoorFair:edit')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DeleteMapping("/{fairId}/jobs/{jobId}")
|
||||
public AjaxResult removeCompanyJob(@PathVariable Long fairId, @PathVariable Long jobId)
|
||||
{
|
||||
ensureFairJobExists(fairId, jobId);
|
||||
outdoorFairJobMapper.delete(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getJobId, jobId));
|
||||
return toAjax(jobService.deleteJobByJobIds(new Long[]{jobId}));
|
||||
}
|
||||
|
||||
private void ensureFairCompanyExists(Long fairId, Long companyId)
|
||||
{
|
||||
Long count = fairCompanyMapper.selectCount(new LambdaQueryWrapper<FairCompany>()
|
||||
.eq(FairCompany::getJobFairId, fairId)
|
||||
.eq(FairCompany::getId, id)));
|
||||
.eq(FairCompany::getCompanyId, companyId));
|
||||
if (count == null || count == 0) {
|
||||
throw new ServiceException("该企业未参加当前招聘会");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairJobExists(Long fairId, Long jobId)
|
||||
{
|
||||
Long count = outdoorFairJobMapper.selectCount(new LambdaQueryWrapper<OutdoorFairJob>()
|
||||
.eq(OutdoorFairJob::getFairId, fairId)
|
||||
.eq(OutdoorFairJob::getJobId, jobId));
|
||||
if (count == null || count == 0) {
|
||||
throw new ServiceException("当前招聘会不存在该岗位");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureFairCompany(Long fairId, Long companyId)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.cms.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 户外招聘会岗位关联。
|
||||
*/
|
||||
@Data
|
||||
@TableName("shz.cms_outdoor_fair_job")
|
||||
public class OutdoorFairJob extends BaseEntity
|
||||
{
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long fairId;
|
||||
|
||||
private Long companyId;
|
||||
|
||||
private Long jobId;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.cms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.cms.domain.Job;
|
||||
import com.ruoyi.cms.domain.OutdoorFairJob;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 户外招聘会岗位关联 Mapper。
|
||||
*/
|
||||
public interface OutdoorFairJobMapper extends BaseMapper<OutdoorFairJob>
|
||||
{
|
||||
List<Job> selectFairJobPage(@Param("fairId") Long fairId,
|
||||
@Param("companyId") Long companyId,
|
||||
@Param("companyName") String companyName,
|
||||
@Param("jobTitle") String jobTitle,
|
||||
@Param("education") String education,
|
||||
@Param("experience") String experience,
|
||||
@Param("offset") int offset,
|
||||
@Param("pageSize") int pageSize);
|
||||
|
||||
Long countFairJobs(@Param("fairId") Long fairId,
|
||||
@Param("companyId") Long companyId,
|
||||
@Param("companyName") String companyName,
|
||||
@Param("jobTitle") String jobTitle,
|
||||
@Param("education") String education,
|
||||
@Param("experience") String experience);
|
||||
|
||||
List<Map<String, Object>> countJobsByCompany(@Param("fairId") Long fairId);
|
||||
}
|
||||
@@ -806,6 +806,7 @@ public class ESJobSearchImpl implements IESJobSearchService
|
||||
|
||||
@Override
|
||||
public void deleteJob(Long jobId) {
|
||||
ensureJobDocumentIndexExists();
|
||||
LambdaEsQueryWrapper<ESJobDocument> lambdaEsQueryWrapper = new LambdaEsQueryWrapper();
|
||||
lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,jobId);
|
||||
esJobDocumentMapper.delete(lambdaEsQueryWrapper);
|
||||
@@ -847,12 +848,31 @@ public class ESJobSearchImpl implements IESJobSearchService
|
||||
if(StringUtil.isEmptyOrNull(job.getCompanyNature())){
|
||||
esJobDocument.setCompanyNature("6");
|
||||
}
|
||||
ensureJobDocumentIndexExists();
|
||||
|
||||
LambdaEsQueryWrapper<ESJobDocument> lambdaEsQueryWrapper = new LambdaEsQueryWrapper();
|
||||
lambdaEsQueryWrapper.eq(ESJobDocument::getJobId,job.getJobId());
|
||||
esJobDocumentMapper.delete(lambdaEsQueryWrapper);
|
||||
esJobDocumentMapper.insert(esJobDocument);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保job_document索引存在;缺失时懒创建(仅建空索引与映射,不重建全量数据)。
|
||||
* process-index-mode: manual 下easy-es不会自动建索引,缺失时delete/update等写操作
|
||||
* 会抛 index_not_found_exception 导致业务接口500并回滚事务,故在写操作前调用以自愈。
|
||||
*/
|
||||
private void ensureJobDocumentIndexExists() {
|
||||
try {
|
||||
if (!esJobDocumentMapper.existsIndex("job_document")) {
|
||||
esJobDocumentMapper.createIndex();
|
||||
logger.info("job_document索引不存在,已自动创建");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 并发场景下其他请求可能已创建索引,忽略“索引已存在”类异常
|
||||
logger.warn("自动创建job_document索引异常(可能已被其他请求创建,忽略)", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ESJobDocument> selectByIds(Long[] jobIds) {
|
||||
LambdaEsQueryWrapper<ESJobDocument> wrapper = new LambdaEsQueryWrapper<>();
|
||||
wrapper.in(ESJobDocument::getJobId,jobIds);
|
||||
|
||||
@@ -40,6 +40,7 @@ public class OutdoorFairServiceImpl extends ServiceImpl<OutdoorFairMapper, Outdo
|
||||
queryWrapper.eq(StringUtils.isNotBlank(outdoorFair.getFairType()), OutdoorFair::getFairType, outdoorFair.getFairType());
|
||||
queryWrapper.eq(StringUtils.isNotBlank(outdoorFair.getRegion()), OutdoorFair::getRegion, outdoorFair.getRegion());
|
||||
queryWrapper.eq(outdoorFair.getVenueId() != null, OutdoorFair::getVenueId, outdoorFair.getVenueId());
|
||||
queryWrapper.eq(outdoorFair.getOnlineApply() != null, OutdoorFair::getOnlineApply, outdoorFair.getOnlineApply());
|
||||
queryWrapper.orderByDesc(OutdoorFair::getHoldTime);
|
||||
queryWrapper.orderByDesc(OutdoorFair::getCreateTime);
|
||||
return list(queryWrapper);
|
||||
|
||||
Reference in New Issue
Block a user