diff --git a/pom.xml b/pom.xml index a968101..325f98b 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,12 @@ ojdbc11 ${ojdbc.version} + + + com.oracle.database.nls + orai18n + ${ojdbc.version} + diff --git a/src/main/java/com/hospital/front/api/order/OrderApi.java b/src/main/java/com/hospital/front/api/order/OrderApi.java index 52d4aca..1565ceb 100644 --- a/src/main/java/com/hospital/front/api/order/OrderApi.java +++ b/src/main/java/com/hospital/front/api/order/OrderApi.java @@ -234,7 +234,7 @@ public class OrderApi { @Mapping("/getResultStatus/{peId}") public Result> getResultStatus(Context ctx, @Path("peId") String peId) { checkBasic(ctx); - return Result.success(service.getResultByPeId(peId)); + return Result.success(service.getResultStatus(peId)); } /** 17. 根据身份证号查询当前年的 peId 和预约日期 */ diff --git a/src/main/java/com/hospital/front/infra/ApiAccessLogFilter.java b/src/main/java/com/hospital/front/infra/ApiAccessLogFilter.java new file mode 100644 index 0000000..cc970c2 --- /dev/null +++ b/src/main/java/com/hospital/front/infra/ApiAccessLogFilter.java @@ -0,0 +1,58 @@ +package com.hospital.front.infra; + +import java.util.Set; + +import org.noear.solon.annotation.Component; +import org.noear.solon.core.handle.Context; +import org.noear.solon.core.handle.Filter; +import org.noear.solon.core.handle.FilterChain; + +import lombok.extern.slf4j.Slf4j; + +/** + * 全局接口访问日志过滤器。 + * + * 统一打印每个请求的调用日志:HTTP 方法、路径、来源 IP、请求参数、耗时、响应状态码。 + * 覆盖所有 Controller(/order、/api/admin),新增接口自动生效,无需逐个手写日志。 + * + * 参数覆盖说明: + * - @Param(query/form)与 @Path(路径变量)统一通过 ctx.paramMap() 取;@Path 的值同时也在 ctx.path() 里可见。 + * - @Body 参数(saveOrder/addUnit)不在此读取,避免消费 body 流破坏 @Body 反序列化; + * 由 service 层日志覆盖(saveOrder 打印 peInfoStr/peItemStr,addUnit 打印 units)。 + * + * 顺序:index = -1 保证本过滤器在 GlobalExceptionHandler / IpWhitelistFilter 之前执行(最外层), + * 从而能记录到异常被处理后写入 ctx 的最终状态码(401/500 等)。 + */ +@Slf4j +@Component(index = -1) +public class ApiAccessLogFilter implements Filter { + + /** 无需打印访问日志的路径(高频健康探测等,避免刷屏) */ + private static final Set SKIP_LOG_PATHS = Set.of("/api/admin/health"); + + @Override + public void doFilter(Context ctx, FilterChain chain) throws Throwable { + long start = System.currentTimeMillis(); + try { + chain.doFilter(ctx); + } finally { + long cost = System.currentTimeMillis() - start; + if (!SKIP_LOG_PATHS.contains(ctx.path())) { + log.info("[接口调用] {} {} ip={} 参数=[{}] cost={}ms status={}", + ctx.method(), ctx.path(), ctx.remoteIp(), paramsOf(ctx), cost, ctx.status()); + } + } + } + + /** 汇总请求参数(query/form/path,@Body 除外),形如 key1=v1, key2=v2 */ + private String paramsOf(Context ctx) { + StringBuilder sb = new StringBuilder(); + for (var kv : ctx.paramMap()) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(kv.getKey()).append('=').append(kv.getFirstValue()); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/hospital/front/infra/exception/GlobalExceptionHandler.java b/src/main/java/com/hospital/front/infra/exception/GlobalExceptionHandler.java index 89d5875..2096c0a 100644 --- a/src/main/java/com/hospital/front/infra/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/hospital/front/infra/exception/GlobalExceptionHandler.java @@ -24,9 +24,21 @@ public class GlobalExceptionHandler implements Filter { try { chain.doFilter(ctx); } catch (org.noear.solon.core.exception.StatusException e) { - // 404/405 等路由状态异常:多为扫描器探测,INFO 一行即可(不打堆栈,避免刷爆日志) - log.info("路由未匹配:{} {}(HTTP {})", ctx.method(), ctx.path(), e.getCode()); - renderStatus(ctx, e.getCode()); + int code = e.getCode(); + if (code >= 500) { + // 5xx 服务端状态异常:真实故障,ERROR 记堆栈 + log.error("服务端状态异常:{} {}(HTTP {})", ctx.method(), ctx.path(), code, e); + } else if (code == 400) { + // 400 请求解析失败(多为 multipart/Content-Type 错误):真实调用失败,WARN 并带 Content-Type 便于定位 + log.warn("请求解析失败(HTTP 400):{} {},Content-Type={}", ctx.method(), ctx.path(), ctx.header("Content-Type")); + } else if (code == 404) { + // 404 路由未匹配:多为扫描器探测,DEBUG 静默(INFO 级别下不再输出) + log.debug("路由未匹配:{} {}(HTTP 404)", ctx.method(), ctx.path()); + } else { + // 其它 4xx(401/403/405/415 等) + log.info("客户端状态异常:{} {}(HTTP {})", ctx.method(), ctx.path(), code); + } + renderStatus(ctx, code); } catch (BusinessException e) { String msg = e.getMessage() != null ? e.getMessage() : "业务异常"; log.warn("业务异常:{}", e.getMessage()); @@ -59,7 +71,11 @@ public class GlobalExceptionHandler implements Filter { * 输出 Result JSON 响应(与原 hospitalmiddle 契约一致:{"success":false,"msg":...})。 */ private void renderResult(Context ctx, Result result) { - ctx.status(200); + // 保留上游预设的 4xx/5xx 状态码(如 checkBasic 认证失败已设 401),否则默认 200。 + // 若强行刷成 200,Basic 认证失败将不再返回 401,浏览器不会弹出账号密码框。 + if (ctx.status() < 400) { + ctx.status(200); + } ctx.contentType("application/json;charset=UTF-8"); ctx.output("{\"success\":" + result.isSuccess() + ",\"msg\":" + toJsonValue(result.getMsg()) diff --git a/src/main/resources/app.yml b/src/main/resources/app.yml index 692ff17..b036818 100644 --- a/src/main/resources/app.yml +++ b/src/main/resources/app.yml @@ -11,10 +11,13 @@ solon: app: name: "hospital-front" group: "hospital" - # JSON 序列化对齐原 hospitalmiddle(Jackson 默认保留 null 字段) + # JSON 序列化对齐原 hospitalmiddle(Jackson 默认保留 null 字段 + 日期格式) serialization: json: nullAsWriteable: true + # 对齐原 spring.jackson.date-format / time-zone:Date/Timestamp 序列化为字符串而非毫秒时间戳 + dateAsFormat: "yyyy-MM-dd HH:mm:ss" + dateAsTimeZone: "GMT+8" # 应用配置 # 医院注册清单由代码决定(各 Adapter 的 @Component),此处仅指定激活哪家 diff --git a/src/main/resources/com/hospital/front/mapper/bsm/BsmOrderMapper.xml b/src/main/resources/com/hospital/front/mapper/bsm/BsmOrderMapper.xml index 000761f..04bae72 100644 --- a/src/main/resources/com/hospital/front/mapper/bsm/BsmOrderMapper.xml +++ b/src/main/resources/com/hospital/front/mapper/bsm/BsmOrderMapper.xml @@ -338,17 +338,7 @@ ID_NO AS "idNo", NAME AS "name", TO_CHAR(audit_date, 'YYYY-MM-DD') AS "auditDate" - FROM ( - SELECT b.pe_id, b.pe_visit_id, a.id_no, a.name, b.unit_code, - (SELECT t.unit_name FROM pe_unit_dict t WHERE t.unit_code = b.unit_code) unit_name, - b.pe_queue_date, b.audit_date, - c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name, - c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context - FROM pe_master_index a, pe_visit b, pe_result_dict c - WHERE a.pe_id = b.pe_id - AND b.pe_id = c.pe_id - AND b.pe_visit_id = c.pe_visit_id - ) + FROM V_PHYEXAM_RESULT