From 82d3264c727f4c59c071437406e580404715f389 Mon Sep 17 00:00:00 2001 From: lapuda <577732344@qq.com> Date: Thu, 25 Jun 2026 23:13:35 +0800 Subject: [PATCH] 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. --- .claude/skills/inspect-shz-logs/SKILL.md | 56 ++++++++ .../references/log-locations.md | 19 +++ .../skills/inspect-shz-logs/scripts/view.sh | 109 ++++++++++++++ .claude/skills/query-shz-highgo/.password | 1 + .claude/skills/query-shz-highgo/SKILL.md | 42 ++++++ .../query-shz-highgo/references/connection.md | 12 ++ .../skills/query-shz-highgo/scripts/query.sh | 43 ++++++ .codex/skills.zip | Bin 16898 -> 0 bytes .../cms/config/OutdoorFairJobMigration.java | 65 +++++++++ .../controller/cms/OutdoorFairController.java | 132 ++++++++++++++++- .../com/ruoyi/cms/domain/OutdoorFairJob.java | 24 ++++ .../cms/mapper/OutdoorFairJobMapper.java | 33 +++++ .../cms/service/impl/ESJobSearchImpl.java | 20 +++ .../service/impl/OutdoorFairServiceImpl.java | 1 + .../mapper/app/OutdoorFairJobMapper.xml | 59 ++++++++ sql/cms_outdoor_fair_8_mock_jobs.sql | 136 ++++++++++++++++++ sql/cms_outdoor_fair_job.sql | 23 +++ sql/hide_indoor_fixed_booth_menu.sql | 10 ++ 18 files changed, 781 insertions(+), 4 deletions(-) create mode 100644 .claude/skills/inspect-shz-logs/SKILL.md create mode 100644 .claude/skills/inspect-shz-logs/references/log-locations.md create mode 100755 .claude/skills/inspect-shz-logs/scripts/view.sh create mode 100644 .claude/skills/query-shz-highgo/.password create mode 100644 .claude/skills/query-shz-highgo/SKILL.md create mode 100644 .claude/skills/query-shz-highgo/references/connection.md create mode 100755 .claude/skills/query-shz-highgo/scripts/query.sh delete mode 100644 .codex/skills.zip create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/config/OutdoorFairJobMigration.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairJob.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairJobMapper.java create mode 100644 ruoyi-bussiness/src/main/resources/mapper/app/OutdoorFairJobMapper.xml create mode 100644 sql/cms_outdoor_fair_8_mock_jobs.sql create mode 100644 sql/cms_outdoor_fair_job.sql create mode 100644 sql/hide_indoor_fixed_booth_menu.sql diff --git a/.claude/skills/inspect-shz-logs/SKILL.md b/.claude/skills/inspect-shz-logs/SKILL.md new file mode 100644 index 0000000..adf4bfc --- /dev/null +++ b/.claude/skills/inspect-shz-logs/SKILL.md @@ -0,0 +1,56 @@ +--- +name: inspect-shz-logs +description: Inspect and follow the SHZ backend's local and production logs through bounded, read-only commands. Use when Claude Code is working in the shz-backend project and needs to diagnose startup failures, exceptions, request behavior, scheduled jobs, or runtime incidents from local backend.log or the production backend, error, info, and user logs on the shz server. +--- + +# Inspect SHZ Logs + +Use the bundled script instead of assembling SSH commands manually. Keep every initial read bounded; follow logs only when the user explicitly asks to monitor live activity. + +## Inspect local logs + +Read the last 200 lines: + +```bash +.claude/skills/inspect-shz-logs/scripts/view.sh local 200 +``` + +Follow the local log interactively: + +```bash +.claude/skills/inspect-shz-logs/scripts/view.sh local 200 --follow +``` + +## Inspect production logs + +Read the main application log: + +```bash +.claude/skills/inspect-shz-logs/scripts/view.sh online backend 200 +``` + +Read the current error log: + +```bash +.claude/skills/inspect-shz-logs/scripts/view.sh online error 200 +``` + +Choose only one production log type: `backend`, `error`, `info`, or `user`. Add `--follow` as the final argument only for an explicit live-monitoring request. + +For keyword analysis, fetch a bounded window and filter locally, for example: + +```bash +.claude/skills/inspect-shz-logs/scripts/view.sh online error 1000 | rg -i 'exception|error|failed' +``` + +## Diagnose from logs + +1. Establish whether the request concerns local or production behavior. +2. Read 100–300 recent lines from the relevant log. +3. Expand up to the script's 5,000-line cap only when timestamps or stack traces require more context. +4. Correlate timestamps, thread names, exception causes, and nearby requests. +5. Report the evidence and likely cause without exposing tokens, passwords, personal data, or unrelated log content. + +Treat production access as read-only. Never restart services, edit files, deploy code, truncate logs, or run arbitrary remote commands through this skill. + +Read [references/log-locations.md](references/log-locations.md) only when checking server identity, paths, or supported log types. diff --git a/.claude/skills/inspect-shz-logs/references/log-locations.md b/.claude/skills/inspect-shz-logs/references/log-locations.md new file mode 100644 index 0000000..03f4b0a --- /dev/null +++ b/.claude/skills/inspect-shz-logs/references/log-locations.md @@ -0,0 +1,19 @@ +# Log locations + +## Local + +- Main log: `/.local/logs/backend.log` +- Producer: `/local.sh` + +## Production: shz + +- SSH: `root@39.98.44.136:6022` +- Application: `/opt/service/project/shz-backend/ruoyi-admin/target/ruoyi-admin.jar` +- Profile: `dev` +- HTTP port: `9091` +- Main process log: `/opt/service/project/shz-backend/logs/backend.log` +- Current error log: `/opt/service/project/shz-backend/logs/sys-error.log` +- Current info log: `/opt/service/project/shz-backend/logs/sys-info.log` +- Current user log: `/opt/service/project/shz-backend/logs/sys-user.log` + +Use only the bundled read-only viewer. Rotated files use date suffixes but are intentionally not exposed by the default script. diff --git a/.claude/skills/inspect-shz-logs/scripts/view.sh b/.claude/skills/inspect-shz-logs/scripts/view.sh new file mode 100755 index 0000000..bc7fd45 --- /dev/null +++ b/.claude/skills/inspect-shz-logs/scripts/view.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +project_dir="$(cd "${skill_dir}/../../.." && pwd)" +local_log="${project_dir}/.local/logs/backend.log" + +ssh_host="root@39.98.44.136" +ssh_port="6022" +remote_log_dir="/opt/service/project/shz-backend/logs" + +usage() { + cat <<'EOF' +Usage: + view.sh local [lines] [--follow] + view.sh online [backend|error|info|user] [lines] [--follow] + +Examples: + view.sh local 200 + view.sh local 100 --follow + view.sh online backend 200 + view.sh online error 500 --follow +EOF +} + +validate_lines() { + local lines="$1" + if [[ ! "${lines}" =~ ^[0-9]+$ ]] || (( lines < 1 || lines > 5000 )); then + echo "Lines must be an integer between 1 and 5000: ${lines}" >&2 + exit 2 + fi +} + +validate_follow() { + local follow="${1:-}" + if [[ -n "${follow}" && "${follow}" != "--follow" ]]; then + echo "Unknown option: ${follow}" >&2 + usage >&2 + exit 2 + fi +} + +view_local() { + local lines="${1:-200}" + local follow="${2:-}" + validate_lines "${lines}" + validate_follow "${follow}" + + if [[ ! -f "${local_log}" ]]; then + echo "Local log does not exist: ${local_log}" >&2 + echo "Start the project first with: ./local.sh start" >&2 + exit 1 + fi + + if [[ "${follow}" == "--follow" ]]; then + tail -n "${lines}" -F -- "${local_log}" + else + tail -n "${lines}" -- "${local_log}" + fi +} + +view_online() { + local kind="${1:-backend}" + local lines="${2:-200}" + local follow="${3:-}" + local remote_file + + case "${kind}" in + backend) remote_file="${remote_log_dir}/backend.log" ;; + error) remote_file="${remote_log_dir}/sys-error.log" ;; + info) remote_file="${remote_log_dir}/sys-info.log" ;; + user) remote_file="${remote_log_dir}/sys-user.log" ;; + *) + echo "Unknown online log type: ${kind}" >&2 + usage >&2 + exit 2 + ;; + esac + + validate_lines "${lines}" + validate_follow "${follow}" + + if [[ "${follow}" == "--follow" ]]; then + ssh -p "${ssh_port}" -o BatchMode=yes -o ConnectTimeout=10 "${ssh_host}" \ + tail -n "${lines}" -F -- "${remote_file}" + else + ssh -p "${ssh_port}" -o BatchMode=yes -o ConnectTimeout=10 "${ssh_host}" \ + tail -n "${lines}" -- "${remote_file}" + fi +} + +target="${1:-}" +case "${target}" in + local) + [[ $# -le 3 ]] || { usage >&2; exit 2; } + view_local "${2:-200}" "${3:-}" + ;; + online) + [[ $# -le 4 ]] || { usage >&2; exit 2; } + view_online "${2:-backend}" "${3:-200}" "${4:-}" + ;; + help|-h|--help) + usage + ;; + *) + usage >&2 + exit 2 + ;; +esac diff --git a/.claude/skills/query-shz-highgo/.password b/.claude/skills/query-shz-highgo/.password new file mode 100644 index 0000000..81bf242 --- /dev/null +++ b/.claude/skills/query-shz-highgo/.password @@ -0,0 +1 @@ +Hello@2026 diff --git a/.claude/skills/query-shz-highgo/SKILL.md b/.claude/skills/query-shz-highgo/SKILL.md new file mode 100644 index 0000000..a59645d --- /dev/null +++ b/.claude/skills/query-shz-highgo/SKILL.md @@ -0,0 +1,42 @@ +--- +name: query-shz-highgo +description: Query the SHZ HighGo Security Enterprise Edition database through its SSH gateway using a guarded read-only psql workflow. Use when Claude Code is working in the shz-backend project and needs to inspect database schemas, tables, columns, row counts, or application data; diagnose data-related behavior; or execute SELECT, WITH, SHOW, or EXPLAIN statements against the project's HighGo database. +--- + +# Query SHZ HighGo + +Use the bundled script to run read-only SQL through the configured SSH gateway. + +## Query workflow + +1. Form the narrowest useful query. Add a small `LIMIT` when inspecting rows. +2. For unfamiliar tables, inspect `information_schema.columns` before selecting business data. +3. Run: + + ```bash + .claude/skills/query-shz-highgo/scripts/query.sh 'SELECT current_database(), current_user, current_schema()' + ``` + +4. Summarize the result and distinguish returned facts from inferences. + +The script accepts only statements beginning with `SELECT`, `WITH`, `SHOW`, or `EXPLAIN`, rejects multiple statements, sets `search_path` to `shz,public`, enforces database-level read-only mode, and applies a 30-second timeout. Never bypass these controls or perform writes unless the user explicitly requests a separate write-capable workflow. + +## Schema discovery + +List user tables: + +```bash +.claude/skills/query-shz-highgo/scripts/query.sh "SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ORDER BY 1, 2" +``` + +Inspect columns: + +```bash +.claude/skills/query-shz-highgo/scripts/query.sh "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'shz' AND table_name = 'lowercase_table_name' ORDER BY ordinal_position" +``` + +HighGo may expose unquoted identifiers in lowercase and quoted identifiers with exact case. Query `information_schema` first when the spelling is uncertain. + +## Connection details + +Read [references/connection.md](references/connection.md) only when troubleshooting connectivity or updating the endpoint. Do not print, quote, or expose the password file. diff --git a/.claude/skills/query-shz-highgo/references/connection.md b/.claude/skills/query-shz-highgo/references/connection.md new file mode 100644 index 0000000..a96f752 --- /dev/null +++ b/.claude/skills/query-shz-highgo/references/connection.md @@ -0,0 +1,12 @@ +# Connection reference + +- SSH gateway: `root@124.243.245.42` +- HighGo client: `/opt/HighGo4.5.7-see/bin/psql` +- Database endpoint: `39.98.44.136:6023` +- Database: `highgo` +- User: `sysdba` +- Default schemas: `shz,public` +- Detected server: HighGo Security Enterprise Edition Database System 4.5.10 +- Password source: `../.password`, mode `600`; override with `HIGHGO_PASSWORD_FILE` + +The SSH gateway must be reachable with the workstation's existing SSH key. Keep credentials out of commands, logs, responses, and `SKILL.md`. diff --git a/.claude/skills/query-shz-highgo/scripts/query.sh b/.claude/skills/query-shz-highgo/scripts/query.sh new file mode 100755 index 0000000..2c88bde --- /dev/null +++ b/.claude/skills/query-shz-highgo/scripts/query.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +password_file="${HIGHGO_PASSWORD_FILE:-$skill_dir/.password}" + +ssh_host="root@124.243.245.42" +psql_path="/opt/HighGo4.5.7-see/bin/psql" +db_host="39.98.44.136" +db_port="6023" +db_name="highgo" +db_user="sysdba" + +if [[ ! -r "$password_file" ]]; then + echo "HighGo password file is missing or unreadable: $password_file" >&2 + exit 2 +fi + +if [[ $# -eq 0 ]]; then + echo "Usage: $0 'SELECT ...'" >&2 + exit 2 +fi + +sql="$*" +if [[ ! "$sql" =~ ^[[:space:]]*(SELECT|select|WITH|with|SHOW|show|EXPLAIN|explain)([[:space:]]|$) ]]; then + echo "Rejected: only SELECT, WITH, SHOW, and EXPLAIN queries are allowed." >&2 + exit 3 +fi + +sql_without_final_semicolon="${sql%;}" +if [[ "$sql_without_final_semicolon" == *';'* ]]; then + echo "Rejected: multiple SQL statements are not allowed." >&2 + exit 3 +fi + +password="$(<"$password_file")" +sql_base64="$(printf '%s' "$sql" | base64 | tr -d '\n')" + +{ + printf '%s\n' "$password" + printf '%s\n' "$sql_base64" +} | ssh -o BatchMode=yes -o ConnectTimeout=10 "$ssh_host" \ + "read -r PGPASSWORD; read -r SQL_BASE64; export PGPASSWORD; export PGOPTIONS='-c default_transaction_read_only=on -c search_path=shz,public -c statement_timeout=30000'; SQL=\$(printf '%s' \"\$SQL_BASE64\" | base64 -d); exec '$psql_path' -X --no-psqlrc -h '$db_host' -p '$db_port' -U '$db_user' -d '$db_name' -v ON_ERROR_STOP=1 -P pager=off -c \"\$SQL\"" diff --git a/.codex/skills.zip b/.codex/skills.zip deleted file mode 100644 index a2dcd36f60460095823f07c97fe0ec65b11c300e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16898 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 relations = fairCompanyMapper.selectList(new LambdaQueryWrapper() .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 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> 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 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 relation = fairCompanyMapper.selectById(id); + if (relation == null || !fairId.equals(relation.getJobFairId())) { + return AjaxResult.error("参会企业不存在"); + } + outdoorFairJobMapper.delete(new LambdaQueryWrapper() + .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() + .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() .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() + .eq(OutdoorFairJob::getFairId, fairId) + .eq(OutdoorFairJob::getJobId, jobId)); + if (count == null || count == 0) { + throw new ServiceException("当前招聘会不存在该岗位"); + } } private void ensureFairCompany(Long fairId, Long companyId) diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairJob.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairJob.java new file mode 100644 index 0000000..040a3af --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/OutdoorFairJob.java @@ -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; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairJobMapper.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairJobMapper.java new file mode 100644 index 0000000..f9133e0 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/mapper/OutdoorFairJobMapper.java @@ -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 +{ + List 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> countJobsByCompany(@Param("fairId") Long fairId); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java index 2f2535c..2cef168 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java @@ -806,6 +806,7 @@ public class ESJobSearchImpl implements IESJobSearchService @Override public void deleteJob(Long jobId) { + ensureJobDocumentIndexExists(); LambdaEsQueryWrapper 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 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 selectByIds(Long[] jobIds) { LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>(); wrapper.in(ESJobDocument::getJobId,jobIds); diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java index 97362ff..4ab28ec 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/OutdoorFairServiceImpl.java @@ -40,6 +40,7 @@ public class OutdoorFairServiceImpl extends ServiceImpl + + + + + WHERE relation.fair_id = #{fairId} + AND (relation.del_flag IS NULL OR relation.del_flag = '0') + AND job.del_flag = '0' + + AND relation.company_id = #{companyId} + + + AND job.company_name LIKE concat( + '%', + cast(#{companyName, jdbcType=VARCHAR} as varchar), + '%' + ) + + + AND job.job_title LIKE concat( + '%', + cast(#{jobTitle, jdbcType=VARCHAR} as varchar), + '%' + ) + + + AND job.education = #{education} + + + AND job.experience = #{experience} + + + + + + + + + diff --git a/sql/cms_outdoor_fair_8_mock_jobs.sql b/sql/cms_outdoor_fair_8_mock_jobs.sql new file mode 100644 index 0000000..2898bb2 --- /dev/null +++ b/sql/cms_outdoor_fair_8_mock_jobs.sql @@ -0,0 +1,136 @@ +BEGIN; + +WITH companies(company_id, company_name, target_count) AS ( + VALUES + (4412::BIGINT, '瑜兴辉煌科技有限公司5', 6), + (3013::BIGINT, '瑜兴辉煌科技有限公司', 15), + (93001::BIGINT, '未知企业', 12), + (93000::BIGINT, '钵施然智能', 11), + (4414::BIGINT, '阿里巴巴', 8), + (4410::BIGINT, 'test616', 15), + (93002::BIGINT, '新疆石达赛特科技有限公司', 13), + (93005::BIGINT, '新疆智图信息科技有限公司', 5), + (93004::BIGINT, '新疆中汇新能环保科技有限公司', 15), + (93003::BIGINT, '新疆微诊医学检验有限公司', 14), + (93007::BIGINT, '街景梦工厂', 7), + (93009::BIGINT, '石河子市稼乐农业生产资料有限责任公司', 9) +), +templates( + seq_no, job_title, min_salary, max_salary, education, experience, + area_code, job_type, vacancies, job_location, description +) AS ( + VALUES + (1, '行政专员', 4000, 6000, '3', '0', '17', '0', 2, '石河子市东城街道', '负责日常行政事务、资料整理及部门协作。'), + (2, '人事专员', 4500, 6500, '4', '4', '16', '0', 2, '石河子市新城街道', '负责招聘、员工关系及人事档案管理。'), + (3, '财务会计', 5000, 7500, '4', '4', '15', '0', 2, '石河子市红山街道', '负责账务处理、报表编制和税务资料整理。'), + (4, '销售顾问', 4500, 9000, '3', '0', '19', '0', 6, '石河子市向阳街道', '负责客户开发、产品介绍、合同跟进和客户维护。'), + (5, '客户服务专员', 4000, 6000, '3', '0', '18', '0', 4, '石河子市老街街道', '负责客户咨询、售后问题处理及服务记录维护。'), + (6, '市场运营专员', 5000, 8000, '4', '4', '17', '0', 3, '石河子市东城街道', '负责市场活动策划、渠道运营和数据分析。'), + (7, '新媒体运营', 5000, 7500, '4', '2', '16', '0', 2, '石河子市新城街道', '负责公众号、短视频平台内容策划与运营。'), + (8, '软件开发工程师', 7000, 12000, '4', '4', '17', '0', 3, '石河子市开发区', '参与业务系统开发、接口联调、测试和技术文档编写。'), + (9, '实施运维工程师', 6000, 9000, '3', '4', '17', '0', 3, '石河子市开发区', '负责系统部署、用户培训、故障处理和日常运维。'), + (10, '设备维修技术员', 5500, 8500, '3', '4', '20', '0', 4, '石河子镇', '负责生产设备巡检、维护保养和故障维修。'), + (11, '质量检验员', 4500, 6500, '3', '0', '20', '0', 4, '石河子镇', '负责原料及产品质量检测、记录和异常反馈。'), + (12, '生产操作员', 4500, 7000, '2', '0', '20', '0', 10, '石河子镇', '按照生产规范完成操作、设备点检及现场整理。'), + (13, '农业技术员', 5000, 8000, '3', '4', '9', '0', 5, '石总场', '提供农业生产技术指导,开展田间巡查和技术服务。'), + (14, '仓储物流专员', 4500, 6500, '3', '0', '21', '0', 4, '北泉镇', '负责货物入库、盘点、出库及物流信息维护。'), + (15, '项目经理', 8000, 13000, '4', '5', '17', '0', 2, '石河子市开发区', '负责项目计划、资源协调、进度控制和交付验收。') +) +INSERT INTO shz.job ( + job_title, min_salary, max_salary, education, experience, + company_name, job_location, posting_date, vacancies, + del_flag, create_by, create_time, update_by, update_time, + company_id, is_hot, apply_num, job_location_area_code, + description, is_publish, data_source, job_type, job_address, + review_status +) +SELECT + templates.job_title, + templates.min_salary, + templates.max_salary, + templates.education, + templates.experience, + companies.company_name, + templates.job_location, + CURRENT_TIMESTAMP, + templates.vacancies, + '0', + 'admin', + CURRENT_TIMESTAMP, + 'admin', + CURRENT_TIMESTAMP, + companies.company_id, + '0', + '0', + templates.area_code, + templates.description, + 1, + '户外招聘会8模拟数据', + templates.job_type, + templates.job_location, + NULL +FROM companies +JOIN templates ON templates.seq_no <= companies.target_count +WHERE NOT EXISTS ( + SELECT 1 + FROM shz.job existing + WHERE existing.company_id = companies.company_id + AND existing.job_title = templates.job_title + AND existing.data_source = '户外招聘会8模拟数据' + AND existing.del_flag = '0' +); + +INSERT INTO shz.cms_outdoor_fair_job ( + fair_id, company_id, job_id, create_by, create_time, + update_by, update_time, del_flag +) +SELECT + 8, + job.company_id, + job.job_id, + 'admin', + CURRENT_TIMESTAMP, + 'admin', + CURRENT_TIMESTAMP, + '0' +FROM shz.job job +WHERE job.data_source = '户外招聘会8模拟数据' + AND job.del_flag = '0' + AND EXISTS ( + SELECT 1 + FROM shz.fair_company fair_company + WHERE fair_company.job_fair_id = 8 + AND fair_company.company_id = job.company_id + ) + AND NOT EXISTS ( + SELECT 1 + FROM shz.cms_outdoor_fair_job relation + WHERE relation.fair_id = 8 + AND relation.job_id = job.job_id + ); + +INSERT INTO shz.job_contact ( + job_id, contact_person, contact_person_phone, position, + del_flag, create_by, create_time, update_by, update_time +) +SELECT + job.job_id, + '招聘负责人', + '13909930008', + '人力资源', + '0', + 'admin', + CURRENT_TIMESTAMP, + 'admin', + CURRENT_TIMESTAMP +FROM shz.job job +WHERE job.data_source = '户外招聘会8模拟数据' + AND job.del_flag = '0' + AND NOT EXISTS ( + SELECT 1 + FROM shz.job_contact contact + WHERE contact.job_id = job.job_id + AND contact.del_flag = '0' + ); + +COMMIT; diff --git a/sql/cms_outdoor_fair_job.sql b/sql/cms_outdoor_fair_job.sql new file mode 100644 index 0000000..6f0b0be --- /dev/null +++ b/sql/cms_outdoor_fair_job.sql @@ -0,0 +1,23 @@ +CREATE SEQUENCE IF NOT EXISTS shz.cms_outdoor_fair_job_id_seq + START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; + +CREATE TABLE IF NOT EXISTS shz.cms_outdoor_fair_job ( + id BIGINT NOT NULL DEFAULT nextval('shz.cms_outdoor_fair_job_id_seq'::regclass) 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' +); + +CREATE UNIQUE INDEX IF NOT EXISTS uk_cms_outdoor_fair_job + ON shz.cms_outdoor_fair_job (fair_id, job_id); + +CREATE INDEX IF NOT EXISTS idx_cms_outdoor_fair_job_company + ON shz.cms_outdoor_fair_job (fair_id, company_id); + +COMMENT ON TABLE shz.cms_outdoor_fair_job IS '户外招聘会岗位关联表'; diff --git a/sql/hide_indoor_fixed_booth_menu.sql b/sql/hide_indoor_fixed_booth_menu.sql new file mode 100644 index 0000000..ab57b38 --- /dev/null +++ b/sql/hide_indoor_fixed_booth_menu.sql @@ -0,0 +1,10 @@ +-- 隐藏并停用左侧菜单“室内固定摊位”。 +-- 保留菜单及角色关联数据,便于需要时恢复。 +UPDATE sys_menu +SET visible = '1', + status = '1', + update_by = 'admin', + update_time = CURRENT_TIMESTAMP, + remark = '已移除左侧菜单入口' +WHERE menu_id = 2112 + AND menu_name = '室内固定摊位';