×
¥
查看详情
🔥 会员专享 文生代码 API

代码优化专家

👁️ 783 次查看
📅 Nov 24, 2025
💡 核心价值: 一键代码体检,结构更清晰、性能更强劲、安全更可靠,让代码整洁易维护!

🎯 可自定义参数(5个)

代码内容或模块
需要审查和优化的完整代码或关键模块内容
优化侧重点
代码优化的重点方向
编程语言
代码所使用的编程语言
框架或库
代码所使用的框架或库
优先关注模块
希望重点优化的功能模块或文件描述

🎨 效果示例

下面是对现有代码的审查要点与优化方案,涵盖性能、结构、可读性、安全、资源效率与错误处理,并附上重构后的示例代码(db.js、userService.js、routes/users.js)。重点解决 SQL 拼接风险、缓存策略、分页默认值、阻塞 I/O 以及 N+1 查询问题。

问题清单与改进建议

  • SQL 拼接与注入风险
    • 现用字符串拼接构造 WHERE 条件,存在注入风险。
    • 使用参数化查询(占位符)替代拼接;复用预编译语句或统一的 all/get/run 方法。
  • 缓存策略
    • 使用全局 cache 对象且无 TTL/大小限制,可能泄漏内存与返回过期数据。
    • 引入有 TTL 与最大容量限制的简易缓存或 LRU 缓存;按 page/pageSize/search 组合键缓存,并限制 pageSize。
  • 分页默认值
    • 默认 pageSize=100 开销大,不适合高并发。
    • 将默认 pageSize 降至 20,并设置最大值(如 100);返回 total 时应执行 COUNT 而非以当前页长度估计。
  • 阻塞 I/O
    • 同步读取 banner.txt 导致事件循环阻塞。
    • 删除无意义的阻塞 I/O;若必须读取,使用异步并缓存结果。
  • N+1 查询
    • 为每个用户单独查询角色导致 N+1。
    • 使用 IN 批量查询角色并在应用层聚合,或一次 LEFT JOIN + 聚合。
  • 错误处理与响应
    • 统一的错误处理中间件;给客户端返回简洁错误信息,日志记录详细信息。
    • 对请求参数做校验,非法参数返回 400。
  • 资源效率
    • 为 SQLite 配置 WAL、foreign_keys、busy_timeout,减少锁冲突与提高并发读性能。
    • 提供 db.close,优雅关闭;避免泄漏。
  • 代码结构与可读性
    • 用 async/await 代替嵌套 Promise;模块职责单一清晰。
    • 在 userService 中构造查询时统一 WHERE 片段与参数数组;避免重复逻辑。

重构后的代码示例

db.js

// db.js
const sqlite3 = require('sqlite3').verbose();
const path = require('path');

const db = new sqlite3.Database(
  path.resolve(__dirname, 'app.db'),
  sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
  (err) => {
    if (err) console.error('Failed to open DB', err);
  }
);

// 基础优化:提高读并发、启用外键、避免长时间锁忙
db.exec(`
  PRAGMA journal_mode = WAL;
  PRAGMA foreign_keys = ON;
  PRAGMA busy_timeout = 3000;
`);

function all(sql, params = []) {
  return new Promise((resolve, reject) => {
    db.all(sql, params, (err, rows) => (err ? reject(err) : resolve(rows)));
  });
}

function get(sql, params = []) {
  return new Promise((resolve, reject) => {
    db.get(sql, params, (err, row) => (err ? reject(err) : resolve(row)));
  });
}

function run(sql, params = []) {
  return new Promise((resolve, reject) => {
    db.run(sql, params, function (err) {
      if (err) return reject(err);
      resolve({ changes: this.changes, lastID: this.lastID });
    });
  });
}

function close() {
  return new Promise((resolve, reject) => {
    db.close((err) => (err ? reject(err) : resolve()));
  });
}

module.exports = { db, all, get, run, close };

userService.js

// userService.js
const { all, get } = require('./db');

// 简易带 TTL 的缓存,限制最大容量防止内存膨胀
const MAX_CACHE_ENTRIES = 100;
const DEFAULT_TTL_MS = 60 * 1000; // 1 分钟

class SimpleCache {
  constructor(max = MAX_CACHE_ENTRIES, ttl = DEFAULT_TTL_MS) {
    this.max = max;
    this.ttl = ttl;
    this.store = new Map();
  }
  set(key, value) {
    const expires = Date.now() + this.ttl;
    this.store.set(key, { value, expires });
    // 简单的 FIFO 清理
    while (this.store.size > this.max) {
      const firstKey = this.store.keys().next().value;
      this.store.delete(firstKey);
    }
  }
  get(key) {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (entry.expires < Date.now()) {
      this.store.delete(key);
      return undefined;
    }
    return entry.value;
  }
}

const cache = new SimpleCache();

function clampPage(page) {
  const p = Number.isFinite(page) && page > 0 ? Math.floor(page) : 1;
  return p;
}

function clampPageSize(size) {
  const s = Number.isFinite(size) && size > 0 ? Math.floor(size) : 20;
  return Math.min(s, 100); // 限制最大 pageSize 防止重查询
}

// 统一的获取用户列表(带分页与搜索),避免 N+1 查询
async function getUsers(page = 1, pageSize = 20, search) {
  page = clampPage(page);
  pageSize = clampPageSize(pageSize);

  const key = `users:${page}:${pageSize}:${search || ''}`;
  const cached = cache.get(key);
  if (cached) return cached;

  const whereParts = [];
  const params = [];

  if (typeof search === 'string' && search.trim().length > 0) {
    const term = search.trim();
    // 可选:限制搜索词长度,避免大 LIKE 导致性能问题
    if (term.length > 100) {
      throw new Error('Search term too long');
    }
    whereParts.push('name LIKE ?');
    params.push(`%${term}%`);
  }

  const whereClause = whereParts.length ? `WHERE ${whereParts.join(' AND ')}` : '';
  const offset = (page - 1) * pageSize;

  // 主查询:分页获取用户
  const users = await all(
    `SELECT id, name, created_at
     FROM users
     ${whereClause}
     ORDER BY created_at DESC
     LIMIT ? OFFSET ?`,
    [...params, pageSize, offset]
  );

  // 总数查询:用于前端分页
  const totalRow = await get(
    `SELECT COUNT(*) AS count FROM users ${whereClause}`,
    params
  );
  const total = totalRow?.count || 0;

  // 批量查询角色,避免 N+1
  const userIds = users.map((u) => u.id);
  let rolesByUser = {};
  if (userIds.length > 0) {
    const placeholders = userIds.map(() => '?').join(',');
    const roleRows = await all(
      `SELECT ur.user_id, r.id AS role_id, r.name AS role_name
       FROM roles r
       JOIN user_roles ur ON ur.role_id = r.id
       WHERE ur.user_id IN (${placeholders})`,
      userIds
    );
    rolesByUser = roleRows.reduce((acc, row) => {
      if (!acc[row.user_id]) acc[row.user_id] = [];
      acc[row.user_id].push({ id: row.role_id, name: row.role_name });
      return acc;
    }, {});
  }

  const data = users.map((u) => ({
    ...u,
    roles: rolesByUser[u.id] || [],
  }));

  const result = { data, total, page, pageSize };
  cache.set(key, result);
  return result;
}

module.exports = { getUsers };

routes/users.js

// routes/users.js
const express = require('express');
const router = express.Router();
const { getUsers } = require('../userService');

// 简化:使用 async 处理器 + 参数校验
router.get('/users', async (req, res, next) => {
  try {
    const pageRaw = Number.parseInt(req.query.page, 10);
    const pageSizeRaw = Number.parseInt(req.query.pageSize, 10);
    const search = req.query.search;

    const page = Number.isFinite(pageRaw) ? pageRaw : 1;
    const pageSize = Number.isFinite(pageSizeRaw) ? pageSizeRaw : 20;

    const result = await getUsers(page, pageSize, search);

    res.json({
      data: result.data,
      total: result.total,
      page: result.page,
      pageSize: result.pageSize,
    });
  } catch (err) {
    // 参数错误或查询错误
    // 可细分:如果是参数错误返回 400,其他返回 500
    if (err && err.message === 'Search term too long') {
      return res.status(400).json({ error: 'search parameter too long' });
    }
    console.error('[GET /users] error:', err);
    res.status(500).json({ error: 'server error' });
  }
});

module.exports = router;

进一步的最佳实践建议

  • 参数校验与安全
    • 对 page、pageSize、search 做严格校验与长度限制;对字符串输入进行 trim 并避免通配符注入,如禁止用户输入原始 % 前后缀,由服务端统一添加。
    • 全部 SQL 使用参数化;不要拼接 user_id、search 等。
  • 缓存策略
    • 列表型数据缓存要短 TTL,并且在有写操作时考虑主动失效(如用户或角色变更时,清理相关 key)。
    • 如对性能要求更高,可采用 lru-cache 包,支持自动 TTL 与更优的淘汰策略。
  • 数据库层优化
    • 针对 users(name)、user_roles(user_id)、user_roles(role_id) 建索引,提高 LIKE 与 JOIN 性能(name LIKE '%term%' 只能用到后缀索引有限,考虑将搜索改为前缀匹配或引入全文索引)。
    • 在高查询量场景下,考虑 better-sqlite3(同步但高性能)或迁移到支持连接池的数据库。
  • 错误处理
    • 在 Express 应用层添加统一错误处理中间件,规范错误响应格式与日志:
      • 记录错误堆栈与请求上下文。
      • 对外返回统一、通用的错误消息,避免泄露内部信息。
    • 对超时、数据库繁忙(busy_timeout)等异常进行重试或友好提示。
  • 资源效率
    • 移除所有不必要的同步 I/O 操作;若必须读取静态资源,应用启动时异步读入并缓存。
    • 在应用关闭时调用 db.close,保证句柄释放。
  • 结构与可维护性
    • 将查询构造封装为可复用的函数(如 buildWhereClause(search))。
    • 统一返回结构:data、total、page、pageSize,前端可直接使用。
    • 使用 async/await,使代码可读性更高,减少回调与嵌套 Promise。

这些改动能显著提高性能与安全性,减少内存风险,提升代码的可维护性与一致性。如果需要,我可以继续将用户创建、更新等写操作也适配为参数化查询并集成缓存失效逻辑。

下面是针对你给出的代码的全面审查与重构建议,重点覆盖安全性、资源效率、可读性、错误处理和性能优化,并附上可直接替换的改进版代码示例。

主要问题与风险

  • 安全性
    • csrf_exempt会暴露跨站请求风险,建议保留CSRF保护或改用鉴权机制。
    • 未校验上传文件类型与内容,容易被伪造图片或压缩炸弹攻击(Decompression Bomb)。
    • 直接使用文件原始名和固定路径,存在路径遍历和信息泄露风险;返回filesystem路径不宜给前端。
  • 资源与性能
    • 无上传大小限制,易导致内存占用过大。
    • 在请求线程中做CPU密集型图像处理,易阻塞并发;考虑异步任务或限流。
    • 未使用stream/缓冲,未控制像素/尺寸,可能触发Pillow解压炸弹。
  • 可读性与可维护性
    • 全局可变状态GLOBAL_COUNTER不安全且不可扩展。
    • 无统一的参数校验与错误处理,导致代码散乱。
    • 日志使用薄弱,不利于故障排查。
  • 文件与路径处理
    • 使用/tmp/uploads固定路径且不安全;应使用Django storage与MEDIA_ROOT。
    • 清理临时文件函数无安全检查,可能误删非目标文件。
  • 错误处理
    • 对无file、尺寸参数异常、Pillow错误、I/O错误未做细分处理。
    • 未使用合适的HTTP状态码(如413用于超过限制)。

重构目标

  • 保留CSRF保护,限制HTTP方法为POST。
  • 参数与内容严格校验,控制上传大小与目标尺寸范围。
  • 使用Django storage与安全文件名,避免信任客户端文件名。
  • 使用统一的错误处理与结构化日志。
  • 提升图像处理效率(合理采样、EXIF旋转),并为后续异步化留接口。
  • 避免返回服务器路径,返回可用的URL或资源标识。

建议的改进版代码(views.py) 说明:

  • 使用require_POST代替csrf_exempt。
  • 使用default_storage保存到MEDIA_ROOT下的uploads子目录。
  • 严格校验size参数与上传大小。
  • Pillow验证图片并处理EXIF方向、限制像素,使用LANCZOS重采样。
  • 生成随机文件名,不信任原始文件名。
  • 统一错误处理与日志记录。
  • 返回URL而非本地路径。

代码: from django.views.decorators.http import require_POST from django.http import JsonResponse from django.core.files.storage import default_storage from django.core.files.base import ContentFile from django.conf import settings import logging, os, re, uuid, time from io import BytesIO from pathlib import Path from PIL import Image, ImageOps, UnidentifiedImageError

logger = logging.getLogger(name)

安全限制与默认参数

MAX_UPLOAD_BYTES = 10 * 1024 * 1024 # 10MB上限,可根据业务调整 MAX_DIMENSION = 4096 # 单边最大像素 DEFAULT_SIZE = (1024, 1024) UPLOAD_SUBDIR = 'uploads' # 在MEDIA_ROOT/uploads之下 Image.MAX_IMAGE_PIXELS = 20_000_000 # 防压缩炸弹(20MP,按需调整)

size_pattern = re.compile(r'^\s*(\d{1,5})x(\d{1,5})\s*$')

def parse_size(raw: str | None) -> tuple[int, int]: if not raw: return DEFAULT_SIZE m = size_pattern.match(raw) if not m: raise ValueError('size参数格式应为 WxH(如 1024x1024)') w, h = int(m.group(1)), int(m.group(2)) if w <= 0 or h <= 0 or w > MAX_DIMENSION or h > MAX_DIMENSION: raise ValueError(f'尺寸超出限制,最大{MAX_DIMENSION}x{MAX_DIMENSION}') return w, h

@require_POST def upload_image(request): # 1) 基本校验 f = request.FILES.get('file') if not f: return JsonResponse({'error': '缺少file文件'}, status=400)

try:
    w, h = parse_size(request.GET.get('size'))
except ValueError as e:
    return JsonResponse({'error': str(e)}, status=400)

# 2) 大小限制(双重:基于属性和实际读取)
if getattr(f, 'size', None) and f.size > MAX_UPLOAD_BYTES:
    return JsonResponse({'error': '文件过大'}, status=413)

data = f.read(MAX_UPLOAD_BYTES + 1)
if len(data) > MAX_UPLOAD_BYTES:
    return JsonResponse({'error': '文件过大'}, status=413)

bio = BytesIO(data)

# 3) 安全打开与验证图片
try:
    img = Image.open(bio)
    img.verify()             # 验证文件头
    bio.seek(0)
    img = Image.open(bio)    # 重新打开用于处理
    img = ImageOps.exif_transpose(img)  # 处理EXIF旋转
except UnidentifiedImageError:
    return JsonResponse({'error': '无效的图片文件'}, status=400)
except Exception as e:
    logger.exception('图片验证失败: %s', e)
    return JsonResponse({'error': '图片处理失败'}, status=400)

# 4) 转换与缩放(CPU密集,必要时迁移到异步任务)
try:
    # 统一输出为JPEG,若需保留格式可根据img.format决定
    img = img.convert('RGB')
    img = img.resize((w, h), resample=Image.Resampling.LANCZOS)
except Exception as e:
    logger.exception('图片缩放失败: %s', e)
    return JsonResponse({'error': '缩放失败'}, status=400)

# 5) 保存到storage,生成安全文件名
buf = BytesIO()
try:
    img.save(buf, format='JPEG', quality=90, optimize=True)
except Exception as e:
    logger.exception('图片编码失败: %s', e)
    return JsonResponse({'error': '图片编码失败'}, status=500)
finally:
    try:
        img.close()
    except Exception:
        pass
buf.seek(0)

filename = f'{uuid.uuid4().hex}_{w}x{h}.jpg'
rel_path = os.path.join(UPLOAD_SUBDIR, filename)

try:
    saved_path = default_storage.save(rel_path, ContentFile(buf.read()))
except Exception as e:
    logger.exception('存储失败: %s', e)
    return JsonResponse({'error': '存储失败'}, status=500)

# 6) 返回URL(避免暴露服务器路径)
try:
    url = default_storage.url(saved_path)
except Exception:
    url = f'{getattr(settings, "MEDIA_URL", "/media/")}{saved_path}'

return JsonResponse({'ok': True, 'url': url, 'width': w, 'height': h})

安全的临时文件清理(仅清理指定目录下、过期的.tmp文件)

def clean_tmp(tmp_dir: str | Path = '/tmp/uploads', max_age_seconds: int = 24 * 3600) -> int: logger = logging.getLogger(name) base = Path(tmp_dir) if not base.exists() or not base.is_dir(): logger.warning('临时目录不存在或不可用: %s', tmp_dir) return 0

now = time.time()
removed = 0
for entry in os.scandir(base):
    try:
        if not entry.is_file(follow_symlinks=False):
            continue
        if not entry.name.endswith('.tmp'):
            continue
        age = now - entry.stat().st_mtime
        if age > max_age_seconds:
            os.unlink(entry.path)
            removed += 1
    except Exception as e:
        logger.warning('删除临时文件失败 %s: %s', entry.path, e)
return removed

设计与实践说明

  • CSRF与HTTP方法:使用require_POST,保留默认CSRF保护。如果该视图用于第三方API,建议改用Django REST Framework并采用Token/JWT鉴权而非csrf_exempt。
  • 上传大小与像素限制:通过f.size与实际read双重限制,降低内存暴涨与DoS风险;设置Image.MAX_IMAGE_PIXELS避免解压炸弹。
  • 内容验证:Pillow的verify后需重新open;同时结合content_type白名单可进一步提升安全性,但最终以内容解析为准。
  • 输出格式:统一转JPEG以降低体积;如需可通过query参数format选择jpeg/png/webp并校验。
  • 文件名与路径:不信任file.name,使用uuid生成文件名,避免路径遍历;使用default_storage抽象存储细节,可无缝切换至云存储。
  • 返回值:返回URL与基本元信息,不返回服务器绝对路径。
  • 日志与错误处理:对不同阶段分别捕获并记录错误,返回语义化HTTP状态码(400/413/500)。
  • 性能与资源:LANCZOS采样质量好但耗CPU,流量大时建议:
    • 使用任务队列(Celery/RQ)将图像处理异步化,视图仅负责接收并排队。
    • 对高并发场景进行限流与超时控制。
    • 合理设置Django的DATA_UPLOAD_MAX_MEMORY_SIZE与FILE_UPLOAD_MAX_MEMORY_SIZE。
  • 临时文件清理:仅清理特定目录下过期的.tmp文件,禁止跟随符号链接;建议改造成管理命令或定时任务,并加年龄阈值。

额外建议(可选)

  • 参数校验可使用Django Form或DRF Serializer,提高一致性和可测试性。
  • 为图片处理添加业务侧的最大宽高与长宽比规则,避免异常参数导致大开销。
  • 增加安全审计与指标埋点(上传失败率、平均处理时延、文件大小分布)。
  • 如果需要保留原图与多规格版本,建议采用“原图+派生图”策略与命名(如uuid_original.ext与uuid_wxh.ext)。

以上改造能在不引入额外依赖的前提下显著提升安全性、可读性与可靠性;对于更高的吞吐与稳定性,建议结合队列异步处理与云存储进一步演进。

下面是针对你现有代码的系统化优化建议与示例改造,重点覆盖结构优化、性能优化、可读性增强、安全性改进与错误处理优化,并结合 Spring/JdbcTemplate 的最佳实践。建议按模块逐步落地,避免一次性大改造成风险。

一、问题诊断(现状与风险)

  • 控制器直接返回原始 Map 列表和纯文本,接口契约不清晰,HTTP 状态码不规范。
  • 缺少输入校验和分页,search 会拉取全部数据,容易发生慢查询与内存压力。
  • SQL 拼接存在注入风险(字符串拼接关键字和字段值)。
  • SimpleDateFormat 作为共享字段且 create 方法加 synchronized,吞吐量差;SimpleDateFormat 本身非线程安全。
  • N+1 查询(逐条订单再查 items),导致大量往返数据库。
  • 同步导出在请求线程执行,阻塞请求并且文件路径硬编码,潜在安全与并发问题。
  • 使用 double 表示金额有精度问题。
  • 错误处理直接 printStackTrace;没有统一异常响应与日志标准。
  • 使用字段注入,增加测试与维护成本。
  • service 层返回字符串,缺少领域模型与 DTO。

二、重构总体思路

  • 分层与数据结构:Controller 只处理 HTTP 交互,Service 负责业务,Repository/DAO 负责SQL访问。使用 DTO 明确输入输出。
  • 注入方式与并发:使用构造器注入,避免共享可变状态;使用 java.time 替代 SimpleDateFormat;移除 synchronized。
  • 校验与分页:引入 Bean Validation(@Valid/@Validated),对 keyword/page/size 等做边界与格式校验;统一默认分页参数。
  • SQL 安全与性能:使用 NamedParameterJdbcTemplate 或 JdbcTemplate 的参数绑定;对 LIKE 做转义;N+1 改为 IN 查询批量加载或一次 JOIN 并在内存聚合;加索引。
  • 导出异步化:将导出移出请求线程,采用异步任务或消息队列;提供任务ID与状态查询;安全的文件路径与文件名。
  • 金额类型:改用 BigDecimal。
  • 错误处理与日志:统一异常处理(@ControllerAdvice),返回结构化错误;使用 SLF4J 统一日志;不要打印堆栈到控制台。
  • 统一响应:通过 ResponseEntity,返回标准 JSON(包含数据与元信息)。

三、示例改造代码(精简可直接落地)

  1. DTO 与响应模型 定义清晰输入输出,避免 Map 到处传递。
package com.example.demo.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;

public class CreateOrderRequest {
    @NotBlank
    @Size(max = 100)
    private String customer;

    @NotNull
    @Positive
    private BigDecimal amount;

    // getters/setters
}

public class OrderItemDto {
    private Long id;
    private Long orderId;
    private String sku;
    private Integer qty;
    private BigDecimal price;
    // getters/setters
}

public class OrderDto {
    private Long id;
    private String customer;
    private BigDecimal amount;
    private OffsetDateTime createdAt;
    private List<OrderItemDto> items;
    // getters/setters
}

public class PageResponse<T> {
    private List<T> content;
    private long total;     // 可选:返回总数,如果需要 count
    private int page;
    private int size;
    // getters/setters, 构造器
}

public class OrderCreatedResponse {
    private Long id;
    private String status;
    // getters/setters, 构造器
}

public class ExportJobResponse {
    private String jobId;
    private String status; // QUEUED/RUNNING/DONE/FAILED
    private String filePath; // 完成后填充
    // getters/setters
}
  1. Controller(构造器注入、校验、分页、标准响应)
package com.example.demo;

import com.example.demo.dto.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/orders")
@Validated
public class OrderController {

    private final OrderService service;

    public OrderController(OrderService service) {
        this.service = service;
    }

    @GetMapping("/search")
    public ResponseEntity<PageResponse<OrderDto>> search(
            @RequestParam(required = false) @Size(max = 100) String keyword,
            @RequestParam(defaultValue = "0") @Min(0) int page,
            @RequestParam(defaultValue = "20") @Min(1) @Max(200) int size) {

        PageResponse<OrderDto> result = service.search(keyword, page, size);
        return ResponseEntity.ok(result);
    }

    @PostMapping("/create")
    public ResponseEntity<OrderCreatedResponse> create(@RequestBody @Validated CreateOrderRequest body) {
        Long id = service.create(body);
        return ResponseEntity.ok(new OrderCreatedResponse(id, "ok"));
    }

    // 将导出独立成异步任务
    @PostMapping("/export")
    public ResponseEntity<ExportJobResponse> export(
            @RequestParam(required = false) @Size(max = 100) String keyword,
            @RequestParam(defaultValue = "0") @Min(0) int page,
            @RequestParam(defaultValue = "100") @Min(1) @Max(1000) int size) {

        ExportJobResponse job = service.exportAsync(keyword, page, size);
        return ResponseEntity.accepted().body(job);
    }

    @GetMapping("/export/{jobId}")
    public ResponseEntity<ExportJobResponse> exportStatus(@PathVariable String jobId) {
        return ResponseEntity.ok(service.getExportStatus(jobId));
    }
}
  1. Service(参数化 SQL、N+1 修复、java.time、异步导出)
package com.example.demo;

import com.example.demo.dto.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.*;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.BufferedWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    private final NamedParameterJdbcTemplate jdbc;
    private final ThreadPoolTaskExecutor executor;

    // 简单内存任务管理示例,生产可替换为持久化
    private final Map<String, ExportJobResponse> jobs = new ConcurrentHashMap<>();

    public OrderService(NamedParameterJdbcTemplate jdbc) {
        this.jdbc = jdbc;
        // 可在配置类中定义更完善的线程池
        this.executor = new ThreadPoolTaskExecutor();
        this.executor.setCorePoolSize(2);
        this.executor.setMaxPoolSize(4);
        this.executor.setQueueCapacity(100);
        this.executor.setThreadNamePrefix("export-");
        this.executor.initialize();
    }

    @Transactional
    public Long create(CreateOrderRequest body) {
        String sql = "INSERT INTO orders(customer, amount, created_at) VALUES(:customer, :amount, :createdAt)";
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("customer", body.getCustomer().trim())
                .addValue("amount", body.getAmount())
                .addValue("createdAt", OffsetDateTime.now(ZoneOffset.UTC)); // 让驱动处理时间类型

        KeyHolder kh = new GeneratedKeyHolder();
        int r = jdbc.update(sql, params, kh);
        if (r <= 0) {
            throw new RuntimeException("Insert failed");
        }
        Number key = kh.getKey();
        return key == null ? null : key.longValue();
    }

    @Transactional(readOnly = true)
    public PageResponse<OrderDto> search(String keyword, int page, int size) {
        String pattern = likePattern(keyword);
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("pattern", pattern)
                .addValue("limit", size)
                .addValue("offset", page * size);

        String baseWhere = pattern == null ? "" : " WHERE customer LIKE :pattern ESCAPE '\\\\' ";
        String sql = "SELECT id, customer, amount, created_at FROM orders" + baseWhere +
                " ORDER BY created_at DESC LIMIT :limit OFFSET :offset";

        List<OrderDto> orders = jdbc.query(sql, params, orderMapper());

        // N+1 修复:批量查询 items
        List<Long> ids = orders.stream().map(OrderDto::getId).collect(Collectors.toList());
        Map<Long, List<OrderItemDto>> itemsMap = Collections.emptyMap();
        if (!ids.isEmpty()) {
            MapSqlParameterSource p2 = new MapSqlParameterSource().addValue("ids", ids);
            String sqlItems = "SELECT id, order_id, sku, qty, price FROM order_items WHERE order_id IN (:ids)";
            List<OrderItemDto> items = jdbc.query(sqlItems, p2, orderItemMapper());
            itemsMap = items.stream().collect(Collectors.groupingBy(OrderItemDto::getOrderId));
        }
        orders.forEach(o -> o.setItems(itemsMap.getOrDefault(o.getId(), Collections.emptyList())));

        // 可选:总数统计(若页面需要)
        long total = countOrders(pattern);

        PageResponse<OrderDto> resp = new PageResponse<>();
        resp.setContent(orders);
        resp.setTotal(total);
        resp.setPage(page);
        resp.setSize(size);
        return resp;
    }

    public ExportJobResponse exportAsync(String keyword, int page, int size) {
        String jobId = UUID.randomUUID().toString();
        ExportJobResponse job = new ExportJobResponse();
        job.setJobId(jobId);
        job.setStatus("QUEUED");
        jobs.put(jobId, job);

        executor.execute(() -> {
            ExportJobResponse j = jobs.get(jobId);
            j.setStatus("RUNNING");
            try {
                PageResponse<OrderDto> data = search(keyword, page, size);
                Path file = Files.createTempFile("orders-", ".csv");
                try (BufferedWriter bw = Files.newBufferedWriter(file)) {
                    // 简易CSV写出,生产建议使用 Commons CSV 并处理转义
                    bw.write("id,customer,amount,created_at\n");
                    for (OrderDto o : data.getContent()) {
                        bw.write(o.getId() + "," + safeCsv(o.getCustomer()) + "," + o.getAmount() + "," + o.getCreatedAt() + "\n");
                    }
                }
                j.setFilePath(file.toAbsolutePath().toString());
                j.setStatus("DONE");
                log.info("Export done: {}", j.getFilePath());
            } catch (Exception e) {
                log.error("Export job failed", e);
                j.setStatus("FAILED");
            }
        });

        return job;
    }

    public ExportJobResponse getExportStatus(String jobId) {
        ExportJobResponse job = jobs.get(jobId);
        if (job == null) {
            ExportJobResponse missing = new ExportJobResponse();
            missing.setJobId(jobId);
            missing.setStatus("NOT_FOUND");
            return missing;
        }
        return job;
    }

    private RowMapper<OrderDto> orderMapper() {
        return (rs, rowNum) -> {
            OrderDto o = new OrderDto();
            o.setId(rs.getLong("id"));
            o.setCustomer(rs.getString("customer"));
            o.setAmount(rs.getBigDecimal("amount"));
            // 建议数据库使用 timestamp with time zone;驱动映射为 OffsetDateTime
            OffsetDateTime odt = rs.getObject("created_at", OffsetDateTime.class);
            o.setCreatedAt(odt);
            return o;
        };
    }

    private RowMapper<OrderItemDto> orderItemMapper() {
        return (rs, rowNum) -> {
            OrderItemDto i = new OrderItemDto();
            i.setId(rs.getLong("id"));
            i.setOrderId(rs.getLong("order_id"));
            i.setSku(rs.getString("sku"));
            i.setQty(rs.getInt("qty"));
            i.setPrice(rs.getBigDecimal("price"));
            return i;
        };
    }

    private long countOrders(String pattern) throws DataAccessException {
        String where = pattern == null ? "" : " WHERE customer LIKE :pattern ESCAPE '\\\\' ";
        String sql = "SELECT COUNT(*) FROM orders" + where;
        MapSqlParameterSource p = new MapSqlParameterSource().addValue("pattern", pattern);
        Long total = jdbc.queryForObject(sql, p, Long.class);
        return total == null ? 0L : total;
    }

    // 处理 LIKE 特殊字符转义,并限制空关键词
    private String likePattern(String keyword) {
        if (keyword == null || keyword.isBlank()) return null;
        String k = keyword.trim();
        // 转义 % 和 _(以 \ 为转义符)
        k = k.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
        return "%" + k + "%";
    }

    private String safeCsv(String s) {
        if (s == null) return "";
        String v = s.replace("\"", "\"\"");
        return "\"" + v + "\"";
    }
}
  1. 统一异常处理(示例)
package com.example.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, IllegalArgumentException.class})
    public ResponseEntity<Object> handleBadRequest(Exception e) {
        log.warn("Bad request", e);
        Map<String, Object> body = new HashMap<>();
        body.put("error", "BAD_REQUEST");
        body.put("message", e.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
    }

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<Object> handleDb(Exception e) {
        log.error("DB error", e);
        Map<String, Object> body = new HashMap<>();
        body.put("error", "DB_ERROR");
        body.put("message", "Database operation failed");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleGeneric(Exception e) {
        log.error("Server error", e);
        Map<String, Object> body = new HashMap<>();
        body.put("error", "INTERNAL_ERROR");
        body.put("message", "Unexpected error");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
    }
}

四、具体优化点清单(便于检查落地)

  • 结构优化
    • 使用 DTO 定义输入输出,避免 Map;统一响应模型。
    • 控制器不参与业务和 SQL;Service 纯业务;未来可抽取 Repository。
    • 构造器注入替代字段注入,利于测试与不可变设计。
  • 性能优化
    • 引入分页参数 page/size 及默认值,限制最大 size。
    • 修复 N+1,通过 IN 批量加载或 JOIN。
    • LIKE 查询转义与合理索引;customer、created_at 建索引;order_items(order_id) 索引。
    • 读操作使用 @Transactional(readOnly=true)。
  • 可读性增强
    • 明确的命名、DTO 字段类型与 RowMapper;移除 printStackTrace。
    • 统一日志规范(info/warn/error)。
  • 安全性改进
    • 所有 SQL 使用参数绑定,避免注入。
    • LIKE 关键字转义;限制 keyword 长度。
    • 导出文件使用临时目录与受控路径;不要使用硬编码绝对路径。
    • 不在日志中输出敏感数据。
  • 错误处理优化
    • 使用 @ControllerAdvice 统一异常响应;返回标准错误码与消息。
    • 使用 ResponseEntity 设置 HTTP 状态码。
    • 对输入进行 Bean Validation。
  • 金额与时间
    • 金额使用 BigDecimal,避免 double 精度问题。
    • 时间使用 OffsetDateTime/Instant;避免 SimpleDateFormat 与 synchronized。
  • 导出异步化
    • 使用线程池或 @Async 将导出移出请求线程;返回 jobId 与状态。
    • 导出内容建议使用 CSV 库处理转义与大数据量(分批写入、流式)。
  • SQL/数据库建议
    • 索引:orders(customer), orders(created_at), order_items(order_id)。
    • 若按金额或时间过滤,考虑复合索引。
    • 大数据分页建议键集分页(基于 created_at+id)提升性能。

五、可选进一步改进

  • 替换 JdbcTemplate 为 JOOQ 或 MyBatis,获得更强的类型安全与可维护性。
  • 接口契约标准化(如返回 code/message/data 格式);引入 OpenAPI/Swagger 文档。
  • 导出使用消息队列(如 RabbitMQ/Kafka)与独立任务执行器,避免服务实例重启丢失任务。
  • 引入审计字段与统一拦截器(created_by、updated_at 等)。
  • 在 Controller 层引入限流与防刷策略,对导出接口做权限与速率限制。

以上改造可以逐步分阶段实施:先分页与参数化 SQL,再修复 N+1,随后统一错误处理与日志,最后迁移导出到异步。这样能快速消除高风险问题,同时稳步提升可维护性与性能。

示例详情

该提示词已被收录:
“程序员必备:提升开发效率的专业AI提示词合集”
让 AI 成为你的第二双手,从代码生成到测试文档全部搞定,节省 80% 开发时间
√ 立即可用 · 零学习成本
√ 参数化批量生成
√ 专业提示词工程师打磨

📖 如何使用

模式 1:即插即用(手动档)
直接复制参数化模版。手动修改 {{变量}} 即可快速发起对话,适合对结果有精准预期的单次任务。
加载中...
💬 模式 2:沉浸式引导(交互档)
一键转化为交互式脚本。AI 将化身专业面试官或顾问,主动询问并引导您提供关键信息,最终合成高度定制化的专业结果。
转为交互式
🚀 模式 3:原生指令自动化(智能档)
无需切换,输入 / 唤醒 8000+ 专家级提示词。 插件将全站提示词库深度集成于 Chat 输入框。基于当前对话语境,系统智能推荐最契合的 Prompt 并自动完成参数化,让海量资源触手可及,从此彻底告别“手动搬运”。
安装插件
🔌 发布为 API 接口
将 Prompt 接入自动化工作流,核心利用平台批量评价反馈引擎,实现"采集-评价-自动优化"的闭环。通过 RESTful 接口动态注入变量,让程序在批量任务中自动迭代出更高质量的提示词方案,实现 Prompt 的自我进化。
发布 API
🤖 发布为 Agent 应用
以此提示词为核心生成独立 Agent 应用,内嵌相关工具(图片生成、参数优化等),提供完整解决方案。
创建 Agent

🕒 版本历史

当前版本
v2.1 2024-01-15
优化输出结构,增强情节连贯性
  • ✨ 新增章节节奏控制参数
  • 🔧 优化人物关系描述逻辑
  • 📝 改进主题深化引导语
  • 🎯 增强情节转折点设计
v2.0 2023-12-20
重构提示词架构,提升生成质量
  • 🚀 全新的提示词结构设计
  • 📊 增加输出格式化选项
  • 💡 优化角色塑造引导
v1.5 2023-11-10
修复已知问题,提升稳定性
  • 🐛 修复长文本处理bug
  • ⚡ 提升响应速度
v1.0 2023-10-01
首次发布
  • 🎉 初始版本上线
COMING SOON
版本历史追踪,即将启航
记录每一次提示词的进化与升级,敬请期待。

💬 用户评价

4.8
⭐⭐⭐⭐⭐
基于 28 条评价
5星
85%
4星
12%
3星
3%
👤
电商运营 - 张先生
⭐⭐⭐⭐⭐ 2025-01-15
双十一用这个提示词生成了20多张海报,效果非常好!点击率提升了35%,节省了大量设计时间。参数调整很灵活,能快速适配不同节日。
效果好 节省时间
👤
品牌设计师 - 李女士
⭐⭐⭐⭐⭐ 2025-01-10
作为设计师,这个提示词帮我快速生成创意方向,大大提升了工作效率。生成的海报氛围感很强,稍作调整就能直接使用。
创意好 专业
COMING SOON
用户评价与反馈系统,即将上线
倾听真实反馈,在这里留下您的使用心得,敬请期待。

试用后开通会员即可无限使用

加载中...