首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

JSP + Servlet 實現動態 JPEG 投票圖表 (圓餅圖) 完整原始碼解析

     編輯:關於JSP

專案架構

  • index.jsp:顯示投票主題與選項,提交至 write.jsp,嵌入 <img src="/vote/servlet/VoteImage"> 顯示即時圓餅圖。
  • write.jsp:處理投票邏輯,更新計數檔案 count.txt,重導向回 index.jsp。
  • VoteImage.java (Servlet):讀取 count.txt,計算百分比,繪製圓餅圖輸出 JPEG。
  • count.txt / question.txt:純文字檔儲存計數與題目 (簡易持久化,生產環境請用資料庫)。

核心程式碼詳細解析

1. index.jsp - 讀取題目、渲染表單、嵌入圖片

<%@ page contentType="text/html;charset=gb2312" %>
<%@ page import="java.io.*" %>
<%
    String realPath = request.getRealPath("question.txt");
    String[] questions = readQuestions(realPath); // 自訂方法讀取 4 行題目
%>
<html><body>
<form method="post" action="write.jsp">
<% for (int i=0; i<4; i++) { %>
    <input type="radio" name="choice" value="<%=i%>"> <%= (char)('A'+i) %>、<%= questions[i] %><br>
<% } %>
<input type="submit" value="我 投 一 票">
<br><img src="servlet/VoteImage" width="150" height="100"> <!-- 動態圖片 -->
</form>
</body></html>

2. write.jsp - 投票計數邏輯

<%@ page import="java.io.*" %>
<%
    String choiceStr = request.getParameter("choice");
    if (choiceStr != null) {
        int choice = Integer.parseInt(choiceStr);
        String countFile = request.getRealPath("count.txt");
        int[] counts = readCounts(countFile); // 讀取 4 行整數
        counts[choice]++;                     // 對應選項 +1
        writeCounts(countFile, counts);       // 寫回檔案
    }
    response.sendRedirect("index.jsp");
%>

3. VoteImage Servlet - 核心繪圖邏輯 (精簡版)

public class VoteImage extends HttpServlet {
    private final Color[] colors = {Color.RED, Color.BLACK, Color.ORANGE, Color.GREEN};

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String countFile = req.getRealPath("count.txt");
        float[][] data = readData(countFile); // [[count, percent], ...]

        // 計算角度
        int[] angles = new int[4];
        for (int i=0; i<3; i++) angles[i] = (int)(data[i][1] * 360);
        angles[3] = 360 - angles[0] - angles[1] - angles[2]; // 修正誤差

        resp.setContentType("image/jpeg");
        resp.setHeader("Cache-Control", "no-store");
        resp.setDateHeader("Expires", 0);

        BufferedImage img = new BufferedImage(150, 100, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, 150, 100);

        // 繪製圓餅圖 (含 3D 傾斜效果)
        AffineTransform at = AffineTransform.getRotateInstance(-20 * Math.PI / 180, 45, 37);
        g2d.setTransform(at);

        int startAngle = 30;
        for (int i=0; i<4; i++) {
            Arc2D arc = new Arc2D.Double(10, 24, 80, 50, startAngle, angles[i], Arc2D.PIE);
            g2d.setColor(colors[i]);
            g2d.fill(arc);
            startAngle += angles[i];
            if (startAngle >= 360) startAngle -= 360;
        }

        // 恢復變換,繪製圖例
        g2d.setTransform(new AffineTransform());
        for (int i=0; i<4; i++) {
            g2d.setColor(colors[i]);
            g2d.fillRect(100, 15*i+20, 10, 10);
            g2d.drawString("" + (char)('A'+i), 120, 15*i+20+8);
        }

        ImageIO.write(img, "JPEG", resp.getOutputStream());
        g2d.dispose();
    }

    // 讀取 count.txt 並計算百分比
    private synchronized float[][] readData(String file) {
        float[][] result = new float[4][2];
        int total = 0;
        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
            for (int i=0; i<4; i++) {
                String line = raf.readLine();
                int val = (line != null) ? Integer.parseInt(line.trim()) : 0;
                result[i][0] = val;
                total += val;
            }
        }
        if (total == 0) total = 1; // 避免除零
        for (int i=0; i<4; i++) result[i][1] = result[i][0] / total;
        return result;
    }
}

關鍵技術亮點

  1. Servlet 輸出二進位圖片:response.setContentType("image/jpeg") + ImageIO.write(..., resp.getOutputStream())。
  2. Graphics2D 進階繪圖:AffineTransform 實現 3D 傾斜視覺效果,Arc2D.PIE 繪製扇形,抗鋸齒渲染提示。
  3. 同步檔案讀寫:synchronized 方法配合 RandomAccessFile 簡易並發控制 (生產環境請改用資料庫交易)。
  4. 快取控制:no-store + Expires=0 確保每次請求獲取最新圖表。

目錄結構部署

webapp/
├── index.jsp
├── write.jsp
├── question.txt      (4 行題目)
├── count.txt         (4 行初始計數)
└── WEB-INF/
    ├── classes/
    │   └── VoteImage.class   (Servlet 編譯後)
    └── web.xml               (需配置 <servlet> <servlet-mapping> VoteImage /servlet/VoteImage)

現代化改造建議

  • 持久化升級:檔案改用資料庫 (含行級鎖/樂觀鎖) 或 Redis 原子計數器。
  • 圖表庫替代:採用 JFreeChart / XChart / ECharts (前端渲染) 降低維護成本。
  • 架構分層:Controller (Spring MVC) → Service (投票邏輯) → View (JSP/Thymeleaf) + 獨立圖表端點。
  • 並發安全:AtomicInteger 或資料庫 UPDATE ... SET count=count+1 替代檔案鎖。

結語

此範例展示了早期 JSP/Servlet 時代「動態圖片 + 簡易持久化」的經典實現模式。雖然檔案儲存與手工繪圖在現代專案已罕見,但 Servlet 輸出二進位流、Graphics2D 向量繪圖、AffineTransform 幾何變換、ImageIO 編碼、快取控制 等核心技術點,至今仍是 Java 圖像處理、驗證碼生成、報表導出的基礎功力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved