本文完整示範一個基於 JSP 與 Servlet 的簡易投票系統,包含題目顯示、投票記錄、動態圖表產生三大核心功能。系統採用文字檔儲存題目與票數,Servlet 利用 Java 2D API 繪製 3D 效果圓餅圖並輸出 JPEG 串流。
question.txt 顯示單選題,內嵌 <img src="/vote/servlet/VoteImage"> 顯示即時圖表。count.txt 計數,重導向回 index.jsp。count.txt,計算百分比,繪製帶陰影旋轉效果的圓餅圖,輸出 JPEG。question.txt(題目)、count.txt(各選項票數)。<%@ page contentType="text/html;charset=gb2312" %>
<% response.setHeader("Cache-Control","no-store"); response.setDateHeader("Expires",0); %>
<%!
public String[] getQuestion(String filePath) {
String[] q = new String[4];
try (RandomAccessFile rf = new RandomAccessFile(filePath, "r")) {
for (int i=0; i<4; i++) {
String line = rf.readLine();
q[i] = (line != null) ? line : "None Question";
}
} catch (Exception e) { e.printStackTrace(); }
return q;
}
%>
<%
String path = request.getRealPath("question.txt");
String[] questions = getQuestion(path);
%>
<html><body>
<table width="180" border="1">
<tr><td align="center">冰帆調查</td></tr>
<form name="frm" method="post" action="write.jsp">
<tr><td>
<%
for (int i=0; i<4; i++) {
out.println("" + (char)('A'+i) + "、" + questions[i] + "
");
}
%>
</td></tr>
<tr><td align="center"><input type="submit" value="我 投 一 票"></td></tr>
<tr><td align="center"><img src="/vote/servlet/VoteImage" width="150" height="100"></td></tr>
</form>
</table>
</body></html>
<%!
public int[] getNumber(String filePath) { ... } // 讀取 count.txt 回傳 int[4]
public void setNumber(String filePath, int[] counts) { ... } // 寫入 count.txt
%>
<%
String param = request.getParameter("choice");
if (param != null) {
int choice = Integer.parseInt(param);
String countPath = request.getRealPath("count.txt");
int[] counts = getNumber(countPath);
counts[choice]++;
setNumber(countPath, counts);
}
response.sendRedirect("index.jsp");
%>
public class VoteImage extends HttpServlet {
private Color[] colors = {Color.RED, Color.BLACK, Color.ORANGE, Color.GREEN};
private int baseAngle = 30;
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String file = req.getRealPath("count.txt");
float[][] data = getNumAndPercent(file); // [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.setHeader("Cache-Control", "no-store");
resp.setContentType("image/jpeg");
ServletOutputStream out = resp.getOutputStream();
BufferedImage img = new BufferedImage(150, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, 150, 100);
// 3D 效果:旋轉變換 + 多層繪製
AffineTransform at = AffineTransform.getRotateInstance(-20*Math.PI/180, 45, 37);
g.setTransform(at);
int r = 6;
int dx = (int)(r * Math.cos((baseAngle + angles[0])/2.0 * Math.PI/180));
int dy = (int)(r * Math.sin((baseAngle + angles[0])/2.0 * Math.PI/180));
Arc2D arc = new Arc2D.Double(10+dx, 24-dy, 80, 50, baseAngle, angles[0], Arc2D.PIE);
g.setColor(colors[0]); g.fill(arc);
int current = baseAngle + angles[0];
for (int i=1; i<4; i++) {
arc = new Arc2D.Double(10, 24, 80, 50, current, angles[i], Arc2D.PIE);
g.setColor(colors[i]); g.fill(arc);
current += angles[i];
if (current > 360) current -= 360;
}
// 恢復變換繪製圖例
g.setTransform(AffineTransform.getRotateInstance(0, arc.getCenterX(), arc.getCenterY()));
for (int i=0; i<4; i++) {
g.setColor(colors[i]);
g.fillRect(100, 15*i+20, 10, 10);
g.drawString("" + (char)('A'+i), 120, 15*i+20+8);
}
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
encoder.encode(img);
out.close();
}
// 讀取計數並計算百分比
private synchronized float[][] getNumAndPercent(String file) { ... }
}
index.jsp、write.jsp、question.txt、count.txt 放在 Web 應用根目錄(如 /vote/)。VoteImage.java 並部屬至 /vote/WEB-INF/classes/(套件結構需符合)。web.xml 註冊 Servlet:
VoteImage VoteImage VoteImage /servlet/VoteImage
https://host:port/vote/index.jsp 測試。question.txt: Yes, I think so! No, I don't think so! Sorry, I don't know the answer! (第四行留空或自訂) count.txt: 12 9 5 9
AffineTransform 旋轉畫布,配合第一塊扇區的位移 (dx, dy) 模擬 3D 立體感。getNumAndPercent 加上 synchronized 避免並發讀寫 count.txt 造成資料不一致。no-store 確保瀏覽器每次重新請求圖表。com.sun.image.codec.jpeg.JPEGCodec(舊版 Sun 專有 API),現代環境建議改用 javax.imageio.ImageIO.write(img, "jpg", out)。gb2312,若環境為 UTF-8 需統一調整。