代码优化专家

451 浏览
50 试用
15 购买
Nov 24, 2025更新

一键代码体检,结构更清晰、性能更强劲、安全更可靠,让代码整洁易维护!

下面是对现有代码的审查要点与优化方案,涵盖性能、结构、可读性、安全、资源效率与错误处理,并附上重构后的示例代码(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,随后统一错误处理与日志,最后迁移导出到异步。这样能快速消除高风险问题,同时稳步提升可维护性与性能。

示例详情

解决的问题

开发者的工作场景描述

解决的问题

针对 对现有代码进行全面审查,提供结构、性能、可读性和安全性改进建议 的日常工作场景,该工具旨在解决以下问题:

  • 代码结构混乱,难以维护和扩展
  • 性能瓶颈和资源浪费问题难以发现
  • 代码可读性差,团队协作效率低

工具介绍

工具名称: 代码优化专家
功能简介: 该提示词帮助开发者对现有代码进行全面审查,提供结构优化、性能提升、可读性增强和安全性改进建议。结合重构技巧、最佳实践和主流编码规范,确保代码高效、整洁且易维护。

协同场景

使用场景描述:

从代码审查到优化实施的全流程协作,确保代码质量持续提升。

具体协作步骤:
  1. 使用代码审查与缺陷识别:识别代码中的潜在问题和缺陷
  2. 结合代码性能优化:分析并解决性能瓶颈问题
  3. 通过代码重构助手:实施结构优化和重构改进
  4. 运用单元测试生成:为优化后的代码生成测试用例
  5. 借助提交信息规范:记录优化过程和变更内容

适用用户

后端开发工程师

借助提示词轻松优化复杂后端逻辑,让API层次结构清晰、性能更高,开发效率显著提升。

初学编程者

通过系统化的改进建议,学习代码优化的最佳实践,快速掌握标准化编程技巧。

软件项目经理

帮助审查团队代码质量,制定更高效的开发规范与优化策略,推动项目高效交付。

特征总结

轻松审查代码结构,精准识别潜在优化点,助力开发者快速提升代码质量。
提供专业的代码重构建议,提升代码性能、可读性与可维护性,让复杂问题简单化。
自动化分析拥抱主流编码规范,确保代码风格统一,减少团队协作冲突。
智能优化错误处理逻辑,助力开发者大幅提升程序的稳定性与鲁棒性。
分享最佳实践,用极高效方式解决资源使用不足与潜在安全漏洞。
深度解析代码逻辑结构,科学组织与模块化,实现在大规模项目中的良好扩展性。
快速识别冗余代码和性能瓶颈,提出高效优化方案。
提供基于行业标准的开发建议,引领开发流程迈向更高水平。

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 161 tokens
- 5 个可调节参数
{ 代码内容或模块 } { 优化侧重点 } { 编程语言 } { 框架或库 } { 优先关注模块 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
限时免费

不要错过!

免费获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59