Browse Source

集成报表生成案例

sangkf 9 months ago
parent
commit
41b3373381

+ 6 - 0
pom.xml

@@ -35,6 +35,12 @@
             <artifactId>lombok</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.5.9</version>
+        </dependency>
+
         <dependency>
             <groupId>gridreport</groupId>
             <artifactId>gridreport</artifactId>

+ 50 - 0
src/main/java/com/hs/gridreport/controller/ReportViewController.java

@@ -0,0 +1,50 @@
+package com.hs.gridreport.controller;
+
+import com.hs.gridreport.data.DataTextProvider;
+import com.hs.gridreport.utils.ReportGenerator;
+import com.hs.gridreport.utils.ServerUtility;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 报表预览
+ *
+ * @author sangkf
+ * @date 2025/1/22 17:37
+ */
+@Controller
+public class ReportViewController {
+
+
+    /**
+     * 报表预览
+     *
+     * @param request
+     * @param response
+     */
+    @GetMapping("/view")
+    public void viewReport(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        try {
+            ReportGenerator reportGenerator = new ReportGenerator(request, response);
+
+            reportGenerator.LoadReport();
+
+            //获取报表文本数据包,约定的JSON或XML格式
+            String reportDataText = DataTextProvider.BuildByHttpRequest(request);
+            if (reportDataText == "") {
+                reportDataText = ServerUtility.BuildFromSelfSQL(reportGenerator.report);
+            }
+
+            reportGenerator.LoadReportData(reportDataText); //载入报表文本数据包
+
+            reportGenerator.Generate(); //生成报表
+        } catch (Exception e) {
+            ServerUtility.ResponseException(response, e.toString());
+        }
+    }
+
+
+}

+ 513 - 0
src/main/java/com/hs/gridreport/data/DataTextProvider.java

@@ -0,0 +1,513 @@
+package com.hs.gridreport.data;
+
+import com.hs.gridreport.entity.ReportQueryItem;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+
+import static com.hs.gridreport.data.GenJsonData.JSON_MultiRecordset;
+import static com.hs.gridreport.data.GenJsonData.JSON_OneRecordset;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 16:49
+ */
+public class DataTextProvider {
+    public static String Build(String QuerySQL) throws Exception {
+        return JSON_OneRecordset(QuerySQL);
+    }
+
+    public static String BuildList(ArrayList<ReportQueryItem> QueryItems) throws Exception {
+        return JSON_MultiRecordset(QueryItems);
+    }
+
+    public static String AppendBlankRow() throws Exception {
+        String QuerySQL = "select m.OrderId, m.OrderDate, d.Productid,p.ProductName,d.Quantity,"
+                + "d.UnitPrice*d.Quantity as Amount "
+                + "from orders m inner join (orderdetails d inner join products p on d.ProductID=p.ProductID) "
+                + "on m.orderid=d.orderid "
+                + "where (m.OrderDate between '1996-1-1' And '1997-9-30') and d.Productid<10 "
+                + "order by d.ProductID";
+        return Build(QuerySQL);
+    }
+
+    public static String Categories() throws Exception {
+        String QuerySQL = "select * from categories ";
+        return Build(QuerySQL);
+    }
+
+    public static String ContractOne() throws Exception {
+        String QuerySQL = "select m.OrderID,m.CustomerId,c.CompanyName,m.OrderDate, "
+                + "p.ProductName,d.UnitPrice,d.Quantity,d.UnitPrice*d.Quantity as Amount "
+                + "from (orders m inner join "
+                + "(orderdetails as d inner join products p on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderID=10252 and d.ProductID=20 "
+                + "order by m.OrderDate, m.OrderID";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTab() throws Exception {
+        String QuerySQL =  "select c.City,m.CustomerId,c.CompanyName,d.ProductID,p.ProductName,"
+                + "d.Quantity, d.UnitPrice*d.Quantity as Amount "
+                + "from (orders m inner join "
+                + "(orderdetails as d inner join products p "
+                + "on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where d.ProductID<8 "
+                + "order by c.City,m.CustomerId, d.ProductID";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTabByDay() throws Exception {
+        String QuerySQL =  "select c.CompanyName,m.OrderDate,d.UnitPrice*d.Quantity as Amount "
+                + "from (orders m inner join orderdetails as d on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderDate between '1997-6-1' and '1997-6-30' "
+                + "order by c.CompanyName, m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTabByMonth() throws Exception {
+        String QuerySQL =  "select c.CompanyName,m.OrderDate,d.UnitPrice*d.Quantity as Amount "
+                + "from (orders m inner join orderdetails as d on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderDate between '1997-1-1' and '1997-12-31' "
+                + "order by c.CompanyName, m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTabCalendar() throws Exception {
+        String QuerySQL =  "select m.OrderDate,sum(d.Quantity) as Qty,sum(d.UnitPrice*d.Quantity) as Amount " +
+                "from (orders m inner join orderdetails as d on m.OrderId=d.OrderId) " +
+                "where m.OrderDate between '1997-1-1' and '1997-12-31' " +
+                "group by m.OrderDate " +
+                "order by m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTabSubtotal() throws Exception {
+        String QuerySQL =  "select t.CategoryName, p.ProductName,c.City,c.CompanyName,d.Quantity " +
+                "from (orders m inner join  " +
+                "(orderdetails as d inner join (products p inner join categories t on p.CategoryID=t.CategoryID) " +
+                "on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) " +
+                "left join customers c on c.CustomerID=m.CustomerID " +
+                "where m.OrderDate between '1997-1-1' and '1997-3-31' " +
+                "order by t.CategoryName,p.ProductName,c.City,c.CompanyName ";
+        return Build(QuerySQL);
+    }
+
+    public static String CrossTabYearMonth() throws Exception {
+        String QuerySQL =
+                "select Year(m.OrderDate) As TheYear,Month(m.OrderDate) As TheMonth, sum(d.UnitPrice*d.Quantity) as Amount " +
+                        "from orders m inner join orderdetails as d on m.OrderId=d.OrderId " +
+                        "group by Year(m.OrderDate),Month(m.OrderDate) " +
+                        "order by Year(m.OrderDate),Month(m.OrderDate) ";
+        return Build(QuerySQL);
+    }
+
+    public static String Customer() throws Exception {
+        String QuerySQL =  "select * from customers order by region,City";
+        return Build(QuerySQL);
+    }
+
+    public static String EmployeeOne() throws Exception {
+        String QuerySQL =  "select * from employees where EmployeeID=5";
+        return Build(QuerySQL);
+    }
+
+    public static String InvoiceMany() throws Exception {
+        String QuerySQL =  "select m.OrderID,m.CustomerId,c.CompanyName,m.OrderDate,M.Freight,"
+                + "d.ProductID,p.ProductName,d.UnitPrice,d.Quantity,d.UnitPrice*d.Quantity as Amount "
+                + "from (orders m inner join "
+                + "(orderdetails as d inner join products p on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderID>=10255 and m.OrderID<10260";
+        return Build(QuerySQL);
+    }
+
+    public static String Picture() throws Exception {
+        String QuerySQL =  "select EmployeeID,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,"
+                + "Address,City,region,PostalCode,Country,HomePhone,Extension,Photo,Notes from employees";
+        return Build(QuerySQL);
+    }
+
+    public static String RTFSample() throws Exception {
+        String QuerySQL =  "select m.OrderID,m.CustomerId,c.CompanyName,c.ContactName,c.Address,c.city,c.region,c.Country,c.Postalcode,"
+                + "m.OrderDate,M.Freight,d.ProductID,p.ProductName,"
+                + "d.UnitPrice,d.Quantity,d.Discount,"
+                + "d.UnitPrice*d.Quantity as Amount,"
+                + "d.UnitPrice*d.Quantity*d.Discount as DiscountAmt,"
+                + "d.UnitPrice*d.Quantity-d.UnitPrice*d.Quantity*d.Discount as NetAmount "
+                + "from (orders m inner join "
+                + "(orderdetails as d inner join products p on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderDate between '1997-1-1' And '1997-1-15' "
+                + "order by m.CustomerID,m.OrderDate, m.OrderID";
+        return Build(QuerySQL);
+    }
+
+    public static String SaleByProduct() throws Exception {
+        String QuerySQL =  "select m.OrderID,m.OrderDate, " +
+                "d.ProductID,p.ProductName,d.UnitPrice,d.Quantity,d.UnitPrice*d.Quantity as Amount  " +
+                "from orders m inner join  " +
+                "(orderdetails as d inner join products p on P.ProductID=D.ProductID) on m.OrderId=d.OrderId " +
+                "where m.OrderDate between '1997-6-1' and '1997-12-31' " +
+                "order by d.ProductID, m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String SaleDetail() throws Exception {
+        String QuerySQL =  "select m.OrderID,m.CustomerId,c.CompanyName,m.OrderDate,M.Freight,"
+                + "d.ProductID,p.ProductName,d.UnitPrice,d.Quantity,d.Discount,"
+                + "d.UnitPrice*d.Quantity as Amount, d.UnitPrice*d.Quantity*d.Discount as DiscountAmt,"
+                + "d.UnitPrice*d.Quantity-d.UnitPrice*d.Quantity*d.Discount as NetAmount "
+                + "from (orders m inner join "
+                + "(orderdetails as d inner join products p on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) "
+                + "left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderID<=10300 "
+                + "order by m.OrderDate, m.OrderID";
+        return Build(QuerySQL);
+    }
+
+    public static String SaleSumByProduct() throws Exception {
+        String QuerySQL =  "select d.Productid,p.ProductName,sum(d.Quantity) as Quantity, " +
+                "sum(d.UnitPrice*d.Quantity*(1-d.Discount)) as Amount " +
+                "from orders m inner join (orderdetails d inner join products p " +
+                "on d.ProductID=p.ProductID) " +
+                "on m.orderid=d.orderid " +
+                "where m.OrderDate between '1997-1-1' and '1997-12-31' " +
+                "group by d.Productid,p.ProductName " +
+                "order by d.Productid";
+        return Build(QuerySQL);
+    }
+
+    public static String Report_7_3g(HttpServletRequest request) throws Exception {
+        String QuerySQL =  "select * from products " +
+                "where ProductID>=" + request.getParameter("BeginNo") + " and ProductID<=" + request.getParameter("EndNo") +
+                " order by ProductID";
+        return Build(QuerySQL);
+    }
+
+    public static String FilterSaleSummary(HttpServletRequest request) throws Exception {
+        String QuerySQL =
+                "select p.CategoryID,c.CategoryName,d.Productid,p.ProductName,sum(d.Quantity) as Quantity," +
+                        "sum(d.UnitPrice*d.Quantity) as Amount " +
+                        "from orders m inner join (orderdetails d inner join (products p inner join categories c on c.CategoryID=p.CategoryID) " +
+                        "on d.ProductID=p.ProductID) on m.orderid=d.orderid " +
+                        "where m.OrderDate between '" + request.getParameter("BeginDate") + "' And '" + request.getParameter("EndDate") + "' " +
+                        "group by p.CategoryID,c.CategoryName,d.Productid,p.ProductName " +
+                        "order by p.CategoryID,d.Productid";
+        return Build(QuerySQL);
+    }
+
+    public static String FilterCustomerProductCrosstab(HttpServletRequest request) throws Exception {
+        String QuerySQL =
+                "select p.CategoryID,t.CategoryName,p.ProductID,p.ProductName,c.City,c.CompanyName,d.Quantity " +
+                        "from (orders m inner join  " +
+                        "(orderdetails as d inner join (products p inner join categories t on p.CategoryID=t.CategoryID) " +
+                        "on p.ProductID=D.ProductID) on m.OrderId=d.OrderId) " +
+                        "left join customers c on c.CustomerID=m.CustomerID " +
+                        "where m.OrderDate between '" + request.getParameter("BeginDate") + "' And '" + request.getParameter("EndDate") + "' " +
+                        "order by t.CategoryName,p.ProductName,c.City,c.CompanyName ";
+        return Build(QuerySQL);
+    }
+
+    public static String FilterCrossPeriod(HttpServletRequest request) throws Exception {
+        String QuerySQL =
+                "select d.ProductID,p.ProductName,m.OrderDate,d.UnitPrice*d.Quantity as Amount " +
+                        "from (orders m inner join orderdetails as d on m.OrderId=d.OrderId) " +
+                        "left join products p on d.ProductID=p.ProductID " +
+                        "where m.OrderDate between '" + request.getParameter("BeginDate") + "' And '" + request.getParameter("EndDate") + "' " +
+                        "order by p.ProductName, m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String FilterChartBar(HttpServletRequest request) throws Exception {
+        String QuerySQL =
+                "select p.CategoryID,t.CategoryName, c.City, sum(d.UnitPrice*d.Quantity) as Amount " +
+                        "from (orders m inner join " +
+                        "(orderdetails as d inner join (products p inner join categories t on p.CategoryID=t.CategoryID) " +
+                        "on P.ProductID=D.ProductID) on m.OrderId=d.OrderId) " +
+                        "left join customers c on c.CustomerID=m.CustomerID " +
+                        "where m.OrderDate '" + request.getParameter("BeginDate") + "' And '" + request.getParameter("EndDate") + "' " +
+                        "group by p.CategoryID,t.CategoryName, c.City " +
+                        "order by c.City,t.CategoryName";
+        return Build(QuerySQL);
+    }
+
+    public static String FilterSaleDetail(HttpServletRequest request) throws Exception {
+        String KeyFilter = "";
+        String CategoryID = request.getParameter("CategoryID");
+        String ProductID = request.getParameter("ProductID");
+        String City = request.getParameter("City");
+        String CompanyName = request.getParameter("CompanyName");
+
+        if (CategoryID != null)
+            KeyFilter += " and p.CategoryID=" + CategoryID;
+        if (ProductID != null)
+            KeyFilter += " and d.Productid=" + ProductID;
+        if (City != null)
+            KeyFilter += " and c.City='" + City + "'";
+        if (CompanyName != null)
+            KeyFilter += " and c.CompanyName='" + CompanyName + "'";
+
+        String QuerySQL =
+                "select p.CategoryID,t.CategoryName,m.OrderId, m.OrderDate,c.City,c.CompanyName,d.Productid,p.ProductName,d.Quantity, " +
+                        "d.UnitPrice * d.Quantity as Amount " +
+                        "from (orders m inner join (orderdetails d inner join " +
+                        "(products p inner join categories t on t.CategoryID = p.CategoryID) " +
+                        "on d.ProductID = p.ProductID) on m.orderid = d.orderid) " +
+                        "inner join customers c on m.CustomerID = c.CustomerID " +
+                        "where m.OrderDate between '" + request.getParameter("BeginDate") + "' And '" + request.getParameter("EndDate") + "'" + KeyFilter + " " +
+                        "order by p.CategoryID,m.OrderDate";
+        return Build(QuerySQL);
+    }
+
+    public static String FreeGridwithDetailGrid() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem("select * from employees where EmployeeID=8", "master"));
+        QueryItems.add(new ReportQueryItem("select * from employees where EmployeeID<8", "detail"));
+        return BuildList(QueryItems);
+    }
+
+    public static String InvoiceOne() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add( new ReportQueryItem("select d.ProductID,p.ProductName,d.UnitPrice,d.Quantity,d.UnitPrice*d.Quantity as Amount "
+                + "from orderdetails as d inner join products p on P.ProductID=D.ProductID "
+                + "where d.OrderID=10255", "Master") );
+        QueryItems.add( new ReportQueryItem("select m.OrderID,m.CustomerId,c.CompanyName,C.Address,m.OrderDate,c.ContactName+c.Phone as Remark "
+                + "from orders m left join customers c on c.CustomerID=m.CustomerID "
+                + "where m.OrderID=10255", "Detail") );
+        return BuildList(QueryItems);
+    }
+
+    public static String SubReport_4a() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem("select * from customers order by CustomerID", "Customer"));
+        QueryItems.add(new ReportQueryItem("select * from products order by ProductName", "Product"));
+        QueryItems.add(new ReportQueryItem(    "select c.CustomerID, c.CompanyName, sum(o.Quantity*o.UnitPrice) As SumAmt " +
+                "from orderdetails o, orders m, customers c " +
+                "where o.OrderID=m.OrderID and m.CustomerID=c.CustomerID " +
+                "group by c.CustomerID, c.CompanyName " +
+                "order by sum(o.Quantity*o.UnitPrice) desc", "Top10Customer"));
+        QueryItems.add(new ReportQueryItem(    "select p.ProductID, p.ProductName, sum(o.Quantity*o.UnitPrice) As SumQty " +
+                "from orderdetails o, products p " +
+                "where o.ProductID=p.ProductID " +
+                "group by p.ProductID, p.ProductName " +
+                "order by sum(Quantity*o.UnitPrice) desc", "Top10Product"));
+        return BuildList(QueryItems);
+    }
+
+    public static String SubReport_4b() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem("select * from customers order by CustomerID", "Customer"));
+        QueryItems.add(new ReportQueryItem("select * from products order by ProductName", "Product"));
+        QueryItems.add(new ReportQueryItem("select * from customers order by CustomerID", "Customer2"));
+        return BuildList(QueryItems);
+    }
+
+    public static String SubReport_4c() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add( new ReportQueryItem("select * from orders where OrderID<=10260 order by OrderID", "Master") );
+        QueryItems.add( new ReportQueryItem("select * from orderdetails where OrderID<=10260", "Detail1") );
+        QueryItems.add( new ReportQueryItem("select o.OrderID, o.ShipCity, c.* from customers c, orders o " +
+                "where OrderID<=10260 and c.City=o.ShipCity " +
+                "order by o.OrderID", "Detail2") );
+        return BuildList(QueryItems);
+    }
+
+    public static String SubReport_4d(HttpServletRequest request) throws Exception {
+        //?这里传汉字参数没做对,如 北京,天津 到这里成为乱码
+        String RawCity = request.getParameter("city");
+        String City = new String(RawCity.getBytes("ISO-8859-1"), "UTF-8");
+        //String City = request.getParameter("city");
+
+        return SubReport_4d(City);
+    }
+    public static String SubReport_4d(String City) throws Exception {
+        String CustomerQuerySQL = "select * from customers where City='" + City + "'";
+        String SupplierQuerySQL = "select * from suppliers where City='" + City + "'";
+
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem(CustomerQuerySQL, "Customer"));
+        QueryItems.add(new ReportQueryItem(SupplierQuerySQL, "Supplier"));
+        return BuildList(QueryItems);
+    }
+
+    public static String SubReport_4e() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem("select * from customers where CustomerID<'B' order by CustomerID", "Customer") );
+        QueryItems.add(new ReportQueryItem("select * from orders where CustomerID<'B' " +
+                "and OrderDate between '1/1/1997' and '12/31/1997' order by OrderID", "Order") );
+        QueryItems.add(new ReportQueryItem("select D.* from orderdetails D, orders M " +
+                "where M.CustomerID<'B' and M.OrderDate between #1/1/1997# and #12/31/1997# and D.OrderID =M.OrderID " +
+                "order by D.OrderID", "OrderDetail") );
+        return BuildList(QueryItems);
+    }
+
+    public static String Chart_8b() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        QueryItems.add(new ReportQueryItem("select * from scatter order by Name, X", "Table1"));
+        QueryItems.add(new ReportQueryItem("select * from scatter order by Name, X", "Table2"));
+        QueryItems.add(new ReportQueryItem("select * from scatter order by Name, X", "Table3"));
+        QueryItems.add(new ReportQueryItem("select * from scatter order by Name, X", "Table4"));
+        return BuildList(QueryItems);
+    }
+
+    public static String Chart_8d() throws Exception {
+        ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+        String SQL = "select c.region,  d.ProductID,p.ProductName, " +
+                "sum(d.UnitPrice * d.Quantity) as Amount " +
+                "from(orders m inner join (orderdetails as d inner join products p on P.ProductID = D.ProductID) on m.OrderId = d.OrderId) " +
+                "left join customers c on c.CustomerID = m.CustomerID " +
+                "where d.ProductID in (1, 10, 11, 21) and m.OrderDate between '1997-1-1' and '1997-12-/31' " +
+                "group by c.region, d.ProductID, p.ProductName " +
+                "order by d.ProductID, c.region";
+        String SQL2 = "select c.region, sum(d.UnitPrice * d.Quantity) as Amount, sum(d.Quantity) as Quantity " +
+                "from(orders m inner join orderdetails d on m.OrderId = d.OrderId) " +
+                "left join customers c on c.CustomerID = m.CustomerID " +
+                "where d.ProductID = 11 and m.OrderDate between '1997-1-1' and '1997-12-/31' " +
+                "group by c.region " +
+                "order by c.region";
+
+        QueryItems.add(new ReportQueryItem(SQL, "Table1"));
+        QueryItems.add(new ReportQueryItem(SQL, "Table2"));
+        QueryItems.add(new ReportQueryItem(SQL, "Table3"));
+        QueryItems.add(new ReportQueryItem(SQL2, "Table4"));
+        return BuildList(QueryItems);
+    }
+
+    public static String BuildByHttpRequest(HttpServletRequest request) throws Exception
+    {
+        String text;
+
+        String dataID = request.getParameter("data");
+        if (dataID == null || dataID.length() == 0) {
+            //throw new Exception("没有提供 'data' 参数,无法获取到报表数据!");
+            String QuerySQL = request.getParameter("QuerySQL");
+            if (QuerySQL != null && QuerySQL.length() > 0) {
+                //根据传递的 HTTP 请求中的查询SQL获取数据
+                text = Build(QuerySQL);
+            }
+            else {
+                int DataLen = request.getContentLength();
+                if (DataLen > 0) {
+                    //从客户端发送的数据包中获取报表查询参数,URL有长度限制,当要传递的参数数据量比较大时,应该采用这样的方式
+                    //这里演示了用这样的方式传递一个超长查询SQL语句。
+                    byte[] DataBuf = new byte[DataLen];
+                    ServletInputStream sif = request.getInputStream();
+                    sif.read(DataBuf, 0, DataLen);
+                    QuerySQL = new String(DataBuf);
+                    text = Build(QuerySQL);
+                }
+                else {
+                    //throw new Exception("没有提供 'data' 参数,无法获取到报表数据!");
+                    text = "";
+                }
+            }
+        }
+        else if (dataID.equals("AppendBlankRow")) {
+            text = AppendBlankRow();
+        }
+        else if (dataID.equals("Categories")) {
+            text = Categories();
+        }
+        else if (dataID.equals("ContractOne")) {
+            text = ContractOne();
+        }
+        else if (dataID.equals("CrossTab")) {
+            text = CrossTab();
+        }
+        else if (dataID.equals("CrossTabByDay")) {
+            text = CrossTabByDay();
+        }
+        else if (dataID.equals("CrossTabByMonth")) {
+            text = CrossTabByMonth();
+        }
+        else if (dataID.equals("CrossTabCalendar")) {
+            text = CrossTabCalendar();
+        }
+        else if (dataID.equals("CrossTabSubtotal")) {
+            text = CrossTabSubtotal();
+        }
+        else if (dataID.equals("CrossTabYearMonth")) {
+            text = CrossTabYearMonth();
+        }
+        else if (dataID.equals("Customer")) {
+            text = Customer();
+        }
+        else if (dataID.equals("EmployeeOne")) {
+            text = EmployeeOne();
+        }
+        else if (dataID.equals("InvoiceMany")) {
+            text = InvoiceMany();
+        }
+        else if (dataID.equals("Picture")) {
+            text = Picture();
+        }
+        else if (dataID.equals("RTFSample")) {
+            text = RTFSample();
+        }
+        else if (dataID.equals("SaleByProduct")) {
+            text = SaleByProduct();
+        }
+        else if (dataID.equals("SaleDetail")) {
+            text = SaleDetail();
+        }
+        else if (dataID.equals("SaleSumByProduct")) {
+            text = SaleSumByProduct();
+        }
+        else if (dataID.equals("Report_7_3g")) {
+            text = Report_7_3g(request);
+        }
+        else if (dataID.equals("FilterSaleSummary")) {
+            text = FilterSaleSummary(request);
+        }
+        else if (dataID.equals("FilterCustomerProductCrosstab")) {
+            text = FilterCustomerProductCrosstab(request);
+        }
+        else if (dataID.equals("FilterCrossPeriod")) {
+            text = FilterCrossPeriod(request);
+        }
+        else if (dataID.equals("FilterChartBar")) {
+            text = FilterChartBar(request);
+        }
+        else if (dataID.equals("FilterSaleDetail")) {
+            text = FilterSaleDetail(request);
+        }
+        else if (dataID.equals("FreeGridwithDetailGrid")) {
+            text = FreeGridwithDetailGrid();
+        }
+        else if (dataID.equals("InvoiceOne")) {
+            text = InvoiceOne();
+        }
+        else if (dataID.equals("SubReport_4a")) {
+            text = SubReport_4a();
+        }
+        else if (dataID.equals("SubReport_4b")) {
+            text = SubReport_4b();
+        }
+        else if (dataID.equals("SubReport_4c")) {
+            text = SubReport_4c();
+        }
+        else if (dataID.equals("SubReport_4d")) {
+            text = SubReport_4d(request);
+        }
+        else if (dataID.equals("SubReport_4e")) {
+            text = SubReport_4e();
+        }
+        else if (dataID.equals("Chart_8b")) {
+            text = Chart_8b();
+        }
+        else if (dataID.equals("Chart_8d")) {
+            text = Chart_8d();
+        }
+        else {
+            throw new Exception("没有为数据 '" + dataID + "' 分配处理程序,无法获取到报表数据!");
+        }
+
+        return text;
+    }
+}

+ 48 - 0
src/main/java/com/hs/gridreport/data/GenDataBase.java

@@ -0,0 +1,48 @@
+package com.hs.gridreport.data;
+
+import com.hs.gridreport.datasource.jdbc_param;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 16:51
+ */
+public class GenDataBase {//简要说明:
+//<!--连接数据库,产生Grid++Report需要的XML格式报表数据-->
+
+    /////////////////////////////////////////////////////////////////////////////////////////////////////////
+//将产生的报表文本数据(XML文本 或 JSON文本)发送给客户端,可以对数据进行压缩
+    public static void ResponseText(HttpServletResponse response, String DataText) throws Exception {
+        response.resetBuffer();
+
+        PrintWriter pw = response.getWriter();
+        pw.print(DataText);
+        pw.close();  //终止后续不必要内容输出
+    }
+
+    //获取 Count(*) SQL 查询到的数据行数。参数 QuerySQL 指定获取报表数据的查询SQL
+    public static int BatchGetDataCount(String QuerySQL) throws Exception {
+        int Total = 0;
+
+        Class.forName(jdbc_param.driver); // Class.forName 装载驱动程序
+        Connection con = DriverManager.getConnection(jdbc_param.url, jdbc_param.user, jdbc_param.password); //用适当的驱动程序类与 DBMS 建立一个连接
+        Statement stmt = con.createStatement(); //用于发送简单的SQL语句
+
+        ResultSet rs = stmt.executeQuery(QuerySQL);
+        if (rs.next()) {
+            Total = rs.getInt(1);
+        }
+        rs.close();
+
+        stmt.close();
+        con.close();
+
+        return Total;
+    }
+}

+ 183 - 0
src/main/java/com/hs/gridreport/data/GenJsonData.java

@@ -0,0 +1,183 @@
+package com.hs.gridreport.data;
+
+import com.hs.gridreport.datasource.jdbc_param;
+import com.hs.gridreport.entity.ReportQueryItem;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.sql.*;
+import java.util.ArrayList;
+
+import static com.hs.gridreport.data.GenDataBase.ResponseText;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:25
+ */
+public class GenJsonData {
+//方法简要说明
+//1. JSON_MultiRecordset:产生多个记录集的 JSON 数据
+//2. JSON_OneRecordset:产生一个记录集的  JSON 数据
+
+    public static String JSON_MultiRecordset(ArrayList<ReportQueryItem> QueryItems) throws Exception {
+        Class.forName(jdbc_param.driver); // Class.forName 装载驱动程序
+        Connection con = DriverManager.getConnection(jdbc_param.url, jdbc_param.user, jdbc_param.password); //用适当的驱动程序类与 DBMS 建立一个连接
+        Statement stmt = con.createStatement(); //用于发送简单的SQL语句
+
+        StringBuffer JsonText = new StringBuffer("{\n");
+        int Count = 1;
+        for (ReportQueryItem QueryItem : QueryItems) {
+            DoGenOneRecordsetText(JsonText, QueryItem.QuerySQL, QueryItem.RecordsetName, stmt, (Count == QueryItems.size()));
+            ++Count;
+        }
+        JsonText.append('}');
+
+        stmt.close();
+        con.close();
+
+        return JsonText.toString();
+    }
+
+
+    public static void JSON_ResponseMultiRecordset(HttpServletResponse response, ArrayList<ReportQueryItem> QueryItems) {
+        try {
+            String text = JSON_MultiRecordset(QueryItems);
+            ResponseText(response, text);
+        } catch (Exception e) {
+            try {
+                //output error message
+                PrintWriter pw = response.getWriter();
+                pw.print(e.toString());
+            } catch (Exception e2) {
+            }
+        }
+    }
+
+    public static String JSON_OneRecordset(String QuerySQL) throws Exception {
+        Class.forName(jdbc_param.driver); // Class.forName 装载驱动程序
+        Connection con = DriverManager.getConnection(jdbc_param.url, jdbc_param.user, jdbc_param.password); //用适当的驱动程序类与 DBMS 建立一个连接
+        Statement stmt = con.createStatement(); //用于发送简单的SQL语句
+
+        StringBuffer JsonText = new StringBuffer("{\n");
+        DoGenOneRecordsetText(JsonText, QuerySQL, "row", stmt, true);
+        JsonText.append('}');
+
+        stmt.close();
+        con.close();
+
+        return JsonText.toString();
+    }
+
+    public static void JSON_ResponseOneRecordset(HttpServletResponse response, String QuerySQL) {
+        try {
+            String text = JSON_OneRecordset(QuerySQL);
+            ResponseText(response, text);
+        } catch (Exception e) {
+            try {
+                //output error message
+                PrintWriter pw = response.getWriter();
+                pw.print(e.toString());
+            } catch (Exception e2) {
+            }
+        }
+    }
+
+    private static void DoGenOneRecordsetText(StringBuffer JsonText, String QuerySQL, String RecordsetName, Statement stmt, boolean LastRecordset) {
+        try {
+            ResultSet rs = stmt.executeQuery(QuerySQL);
+
+            ResultSetMetaData rsmd = rs.getMetaData();
+            int ColCount = rsmd.getColumnCount();
+
+            JsonText.append('"');
+            JsonText.append(RecordsetName);
+            JsonText.append("\":[\n");
+            boolean First = true;
+            while (rs.next()) {
+                if (First)
+                    First = false;
+                else
+                    JsonText.append(",\n");
+                JsonText.append('{');
+                for (int i = 1; i <= ColCount; i++) {
+                    JsonText.append('"');
+                    JsonText.append(rsmd.getColumnLabel(i));
+                    JsonText.append("\":\"");
+
+                    int ColType = rsmd.getColumnType(i);
+                    if (ColType == Types.LONGVARBINARY || ColType == Types.VARBINARY || ColType == Types.BINARY || ColType == Types.BLOB) {
+                        byte[] BinData = rs.getBytes(i);
+                        if (!rs.wasNull()) {
+                            String base64Str = (new sun.misc.BASE64Encoder()).encode(BinData);
+                            base64Str = base64Str.replaceAll("\n", "").replaceAll("\r", ""); //\r\n字符要去掉,不然HTML5报表解析json数据不成功
+                            JsonText.append(base64Str);
+                        }
+                    } else {
+                        String Val = rs.getString(i);
+                        if (!rs.wasNull()) {
+                            if (JSON_HasSpecialChar(Val))
+                                JsonText.append(JSON_Encode(Val));
+                            else
+                                JsonText.append(Val);
+                        }
+                    }
+
+                    JsonText.append('"');
+                    if (i < ColCount)
+                        JsonText.append(',');
+                }
+                JsonText.append('}');
+            }
+            JsonText.append("\n]");
+            if (!LastRecordset)
+                JsonText.append(',');
+            JsonText.append('\n');
+
+            rs.close();
+        } catch (Exception e) {
+        }
+    }
+
+    //判断是否包含JSON特殊字符
+    public static boolean JSON_HasSpecialChar(String text) {
+        if (text == null)
+            return false;
+
+        boolean ret = false;
+        int len = text.length();
+        for (int i = 0; i < len; ++i) {
+            char ch = text.charAt(i);
+            if (ch == '"' || ch == '\\' || ch == '\r' || ch == '\n' || ch == '\t') {
+                ret = true;
+                break;
+            }
+        }
+
+        return ret;
+    }
+
+    //判断是否包含JSON特殊字符
+    public static String JSON_Encode(String text) {
+        int len = text.length();
+        StringBuffer results = new StringBuffer(len + 20);
+
+        for (int i = 0; i < len; ++i) {
+            char ch = text.charAt(i);
+            if (ch == '"' || ch == '\\' || ch == '\r' || ch == '\n' || ch == '\t') {
+                results.append('\\');
+                if (ch == '"' || ch == '\\')
+                    results.append(ch);
+                else if (ch == '\r')
+                    results.append('r');
+                else if (ch == '\n')
+                    results.append('n');
+                else if (ch == '\t')
+                    results.append('t');
+            } else {
+                results.append(ch);
+            }
+        }
+
+        return results.toString();
+    }
+}

+ 8 - 0
src/main/java/com/hs/gridreport/datasource/jdbc_param.java

@@ -0,0 +1,8 @@
+package com.hs.gridreport.datasource;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:28
+ */
+public class jdbc_param extends mysql_jdbc_param {
+}

+ 15 - 0
src/main/java/com/hs/gridreport/datasource/mssql_jdbc_param.java

@@ -0,0 +1,15 @@
+package com.hs.gridreport.datasource;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:33
+ */
+public class mssql_jdbc_param{
+    public final static String driver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";   //mssql2000 jdbc
+    public final static String url = "jdbc:microsoft:sqlserver://localhost;DatabaseName=gridreport"; //mssql2000 jdbc
+    //如果是应用mssql20005的jdbc驱动,应该注视掉上面两行,而去掉下面两行的注视。说明:mssql2000 jdbc可以连接mssql20005 数据库
+    //public final static String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";    //mssql2005 jdbc
+    //public final static String url = "jdbc:sqlserver://localhost;DatabaseName=gridreport"; //mssql2005 jdbc
+    public final static String user = "sa";
+    public final static String password = "";
+}

+ 15 - 0
src/main/java/com/hs/gridreport/datasource/mysql_jdbc_param.java

@@ -0,0 +1,15 @@
+package com.hs.gridreport.datasource;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:31
+ */
+public class mysql_jdbc_param {
+
+    public final static String driver = "com.mysql.jdbc.Driver";
+    //public final static String url = "jdbc:mysql://localhost/gridreport?user=root&password=&useUnicode=true&characterEncoding=utf8";
+    public final static String url = "jdbc:mysql://localhost/gridreport?useUnicode=true&characterEncoding=utf8";
+    public final static String user = "root";
+    public final static String password = "";
+
+}

+ 12 - 0
src/main/java/com/hs/gridreport/datasource/odbc_jdbc_param.java

@@ -0,0 +1,12 @@
+package com.hs.gridreport.datasource;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:33
+ */
+public class odbc_jdbc_param {
+    public final static String driver = "sun.jdbc.odbc.JdbcOdbcDriver";
+    public final static String url = "jdbc:odbc:webreport";
+    public final static String user = "sa";
+    public final static String password = "";
+}

+ 12 - 0
src/main/java/com/hs/gridreport/datasource/oracle_jdbc_param.java

@@ -0,0 +1,12 @@
+package com.hs.gridreport.datasource;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 17:32
+ */
+public class oracle_jdbc_param {
+    public final static String driver = "oracle.jdbc.driver.OracleDriver";
+    public final static String url = "jdbc:oracle:thin:@localhost:1521";
+    public final static String user = "hr";
+    public final static String password = "hr";
+}

+ 20 - 0
src/main/java/com/hs/gridreport/entity/ReportQueryItem.java

@@ -0,0 +1,20 @@
+package com.hs.gridreport.entity;
+
+import lombok.Data;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 16:51
+ */
+public class ReportQueryItem {
+
+    public String QuerySQL;
+    public String RecordsetName;
+
+    public ReportQueryItem(String AQuerySQL, String ARecordsetName)
+    {
+        QuerySQL = AQuerySQL;
+        RecordsetName = ARecordsetName;
+    }
+
+}

+ 84 - 0
src/main/java/com/hs/gridreport/utils/ReportGenerateInfo.java

@@ -0,0 +1,84 @@
+package com.hs.gridreport.utils;
+
+import gridreport.jni.ExportImageType;
+import gridreport.jni.ExportType;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 16:47
+ */
+public class ReportGenerateInfo {
+    public String contentType;          //HTTP响应ContentType
+    public String extFileName;          //默认扩展文件名
+    public boolean isGRD;               //是否生成为 Grid++report 报表文档格式
+    public ExportType exportType;     //导出的数据格式类型
+    public ExportImageType imageType; //导出的图像格式类型
+
+    ///根据报表导出格式类型,生成对应的响应信息,将结果信息保存本类的成员变量中
+    ///参数 exportTypeText: 指定报表导出的导出格式类型
+    ///参数 imageTypeText: 指定生成的图像格式,仅当为导出图像时有效
+    public void Build(String exportTypeText, String imageTypeText)
+    {
+        extFileName = exportTypeText;
+        contentType = "application/";
+        isGRD = (exportTypeText == "grd" || exportTypeText == "grp");
+
+        if (isGRD) {
+            contentType += "octet-stream"; //application/octet-stream ?application/grd
+        }
+        else {
+            switch (exportTypeText) {
+                case "xls":
+                    exportType = ExportType.XLS;
+                    contentType += "x-xls"; //application/vnd.ms-excel application/x-xls
+                    break;
+                case "csv":
+                    exportType = ExportType.CSV;
+                    contentType += "vnd.ms-excel"; //application/vnd.ms-excel application/x-xls
+                    break;
+                case "txt":
+                    exportType = ExportType.TXT;
+                    contentType = "text/plain"; //text/plain
+                    break;
+                case "rtf":
+                    exportType = ExportType.RTF;
+                    contentType += "rtf"; //application/rtf
+                    break;
+                case "img":
+                    exportType = ExportType.IMG;
+                    //contentType 要在后面根据图像格式来确定
+                    break;
+                default:
+                    extFileName = "pdf"; //"type"参数如没有设置,保证 extFileName 被设置为"pdf"
+                    exportType = ExportType.PDF;
+                    contentType += "pdf";
+                    break;
+            }
+
+            //导出图像处理
+            if (exportType == ExportType.IMG)
+            {
+                extFileName = imageTypeText;
+                switch (imageTypeText) {
+                    case "bmp":
+                        imageType = ExportImageType.BMP;
+                        contentType += "x-bmp";
+                        break;
+                    case "jpg":
+                        imageType = ExportImageType.JPEG;
+                        contentType += "x-jpg";
+                        break;
+                    case "tif":
+                        imageType = ExportImageType.TIFF;
+                        contentType = "image/tiff";
+                        break;
+                    default:
+                        extFileName = "png";
+                        imageType = ExportImageType.PNG;
+                        contentType += "x-png";
+                        break;
+                }
+            }
+        }
+    }
+}

+ 118 - 0
src/main/java/com/hs/gridreport/utils/ReportGenerator.java

@@ -1,8 +1,126 @@
 package com.hs.gridreport.utils;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 /**
  * @author sangkf
  * @date 2025/1/22 16:42
  */
 public class ReportGenerator {
+
+    protected HttpServletRequest request;
+    protected HttpServletResponse response;
+
+    public gridreport.jni.Report report = null;
+
+    public ReportGenerator(HttpServletRequest _request, HttpServletResponse _response) throws Exception {
+        this.request = _request;
+        this.response = _response;
+
+        //必须在 ServerUtility.jsp 中根据 grid++report 的程序模块实际部署目录设置gridreport_module_path
+        gridreport.jni.Report.ConfigModulePath(ServerUtility.gridreport_module_path);
+
+        //必须在 ConfigModulePath 之后才能创建报表对象
+        report = new gridreport.jni.Report();
+    }
+
+    //根据HTTP请求中的 report 参数加载报表模板
+    public void LoadReport() throws Exception {
+        String reportID = request.getParameter("report");
+
+        if (reportID == null || reportID.length() == 0)
+            throw new Exception("没有在URL参数中指定\"report\"参数!");
+
+        //载入报表模板。模板路径获取应该按实际情况进行调整
+        //String reportPathFile = _context.Server.MapPath("../../grf/") + reportID + ".grf"; //根据当前页进行相对寻址
+        //String reportPathFile = _context.Server.MapPath("/grf/") + reportID + ".grf";     //根据WEB服务器根目录寻址
+        String reportPathFile = request.getRealPath("grf") + "/" + reportID + ".grf";
+        LoadReportEx(reportPathFile);
+    }
+
+    //根据路径文件名加载报表模板,参数为当前网页的相对文件路径名
+    public void LoadReport(String pathFile) throws Exception {
+        //如果第一个字符是“/”,则从根目录寻址,反之从当前网页所在目录相对寻址
+        String reportPathFile = pathFile.charAt(0) == '/' ?
+                request.getRealPath(pathFile) :
+                new java.io.File(request.getRealPath(request.getServletPath())).getParent() + "/" + pathFile;
+        LoadReportEx(reportPathFile);
+    }
+
+    //根据完整的路径文件名加载报表模板
+    public void LoadReportEx(String fullPathFile) throws Exception {
+        Boolean success = report.LoadFromFile(fullPathFile);
+        if (!success)
+            throw new Exception("载入报表模板 '" + fullPathFile + "' 失败!");
+
+        //如果要禁止用拉模式获取报表数据,需要将报表模板中的数据连接串置空
+        //如果确实要用拉模式获取报表数据,请将以下代码注释掉
+        //report.setConnectionString("");
+        //if (report.getDetailGrid() != null)
+        //    report.getDetailGrid().getRecordset().setConnectionString("");
+    }
+
+    //从 XML 或 JSON 文本数据包加载报表数据。数据形式必须满足Grid++report的约定要求。
+    public void LoadReportData(String DataText) throws Exception {
+        Boolean success = report.LoadDataFromXML(DataText);
+        if (!success)
+            throw new Exception("载入报表失败, 数据:\n" + DataText);
+    }
+
+    //生成报表结果到二进制数据包对象中,并将数据响应给请求的客户端
+    //HTTP请求中包含的参数:
+    //report: 指定哪个报表
+    //type: 指定生成的数据类型,可选[pdf|xls|csv|txt|rtf|img|grd|grp]。如果不指定,默认为pdf
+    //img: 指定生成的图像数据格式,仅当生成图像数据时需要,可选[png|bmp|jpg|tif]。如果不指定,默认为png
+    //open: 指定生成的数据打开模式,可选[inline|attachment],"inline"表示在网页中内联显示,"attachment"表示以附件文件形式下载。如果不指定,由浏览器自动确定打开方式
+    //filename: 指定下载(或保存)文件时的默认文件名称
+    public void Generate() throws Exception {
+        String typeText = request.getParameter("type");
+        String imageTypeText = request.getParameter("img");
+        String fileName = request.getParameter("filename");
+        String openMode = request.getParameter("open");
+
+        Generate(typeText, fileName, openMode, imageTypeText);
+    }
+
+    public void Generate(String typeText, String fileName) throws Exception {
+        Generate(typeText, fileName, "", "");
+    }
+
+    public void Generate(String typeText, String fileName, String openMode, String imageTypeText) throws Exception {
+        //确定导出数据类型及数据的ContentType
+        ReportGenerateInfo generateInfo = new ReportGenerateInfo();
+        generateInfo.Build(typeText, imageTypeText);
+
+        gridreport.jni.BinaryObject bo;
+        if (generateInfo.isGRD) {
+            bo = report.GenerateDocumentData();
+        } else {
+            gridreport.jni.ExportOption exportOption = report.PrepareExport(generateInfo.exportType);
+
+            if (generateInfo.exportType == gridreport.jni.ExportType.IMG) {
+                gridreport.jni.E2IMGOption E2IMGOption = exportOption.getAsE2IMGOption();
+                E2IMGOption.setImageType(generateInfo.imageType);
+                E2IMGOption.setAllInOne(true); //所有页产生在一个图像文件中
+                //E2IMGOption.setVertGap(20);    //页之间设置20个像素的间距
+            }
+
+            bo = report.ExportToBinaryObject();
+            report.UnprepareExport();
+        }
+
+        //响应生成的报表结果数据
+        //如果参数中没指定文件名,则用报表模板中的“标题”属性设置一个默认文件名
+        if (fileName == null || fileName.length() == 0) {
+            if (report.getTitle().length() == 0)
+                fileName = "gridreport";
+            else
+                fileName = report.getTitle();
+            fileName += "." + generateInfo.extFileName;
+        }
+
+        ServerUtility.ResponseBinary(request, response, bo, fileName, generateInfo.contentType, openMode);
+    }
 }
+

+ 254 - 0
src/main/java/com/hs/gridreport/utils/ServerUtility.java

@@ -0,0 +1,254 @@
+package com.hs.gridreport.utils;
+
+import com.hs.gridreport.data.DataTextProvider;
+import com.hs.gridreport.entity.ReportQueryItem;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.util.ArrayList;
+
+/**
+ * @author sangkf
+ * @date 2025/1/22 16:46
+ */
+public class ServerUtility {
+
+    //如果是在 Windows 服务器,此参数不起作用,可以设定为任意值
+    //如果是在 Linux 服务器,此参数必须与实际相符。指定目录下必须有 fonts 与 lang 这两个子目录
+    //根据 grid++report 的程序模块实际部署目录设置 gridreport_module_path
+    static public String gridreport_module_path = "/usr/local/grsvr6"; //用于默认发布
+
+    // 将报表生成的二进制数据响应给 HTPP 请求客户端
+    // <param name="context"> HTPP 请求对象</param>
+    // <param name="ExportResult">报表生成的二进制数据</param>
+    // <param name="FileName">指定下载(或保存)文件时的默认文件名称</param>
+    // <param name="contentType">响应的ContentType</param>
+    // <param name="OpenMode">指定生成的数据打开模式,可选[inline|attachment],"inline"表示在网页中内联显示,"attachment"表示以附件文件形式下载。如果不指定,由浏览器自动确定打开方式。</param>
+    static public void ResponseBinary(HttpServletRequest request, HttpServletResponse response,
+                                      gridreport.jni.BinaryObject bo, String FileName, String contentType, String OpenMode) throws Exception {
+        if (bo.getDataSize() > 0) {
+            String Disposition = "";
+
+            if (OpenMode != null && OpenMode.length() > 0)
+                Disposition = OpenMode + "; ";
+
+            String UserAgent = request.getHeader("User-Agent"); //?这样是否可行 context.Request.UserAgent
+            Disposition += EncodeAttachmentFileName(UserAgent, FileName);
+
+            response.setContentType(contentType);
+            response.addHeader("Content-Length", new Integer(bo.getDataSize()).toString());
+            response.addHeader("Content-Disposition", Disposition);
+
+            response.resetBuffer();
+
+            ServletOutputStream os = response.getOutputStream();
+            os.write(bo.getDataBuf());
+            os.flush();
+        } else {
+            ResponseException(response, "Failed to generate report.");
+        }
+    }
+
+    // 将异常信息文字响应给请求的客户端
+    // <param name="context"></param>
+    // <param name="MessageText"></param>
+    public static void ResponseException(HttpServletResponse response, String MessageText) throws Exception {
+        PrintWriter pw = response.getWriter();
+        pw.print(MessageText);
+        pw.close();  //终止后续不必要内容输出
+    }
+
+    // 为了文件名中的汉字与特殊字符能正确,必须进行分浏览器处理
+    // <param name="browserAgent"></param>
+    // <param name="RawFileName"></param>
+    // <returns></returns>
+    public static String EncodeAttachmentFileName(String browserAgent, String RawFileName) throws Exception {
+        String EncodedFileName = URLEncoder.encode(RawFileName, "utf-8");
+
+        // 如果没有browserAgent,则默认使用IE的方式进行编码,因为毕竟IE还是占多数的
+        String ret = "filename=\"" + EncodedFileName + "\"";
+        if (browserAgent != null && browserAgent.length() > 0) {
+            browserAgent = browserAgent.toLowerCase();
+            // msie 与 edge 采用默认的方式
+            if (!browserAgent.contains("msie") && !browserAgent.contains("edge")) {
+                // Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出
+                if (browserAgent.contains("applewebkit")) {
+                    //TODO...EncodedFileName = MimeUtility.encodeText(RawFileName, "UTF8", "B"); //?javax.mail.internet.MimeUtility这个不能用,造成chrome下载不成功
+                    //ret = "filename*=UTF-8''" + EncodedFileName.replaceAll("\\+", "%20");
+                    //ret = "filename=" + EncodedFileName.replaceAll("\\+", "%20");
+                    //ret = "filename=\"" + RawFileName + "\"";
+                }
+                // Safari浏览器,只能采用ISO编码的中文输出
+                else if (browserAgent.contains("safari")) {
+                    //28591  iso-8859-1                1252   *
+                    ret = "filename=\"" + new String(EncodedFileName.getBytes("UTF-8"), "ISO8859-1") + "\"";
+                    //byte[] UTF8Bytes = UTF8Encoding.GetBytes(RawFileName);
+                    //String ISO8859Text = System.Text.Encoding.GetEncoding(28591).GetString(UTF8Bytes);
+                    //ret = "filename=\"" + ISO8859Text + "\"";
+                }
+                // Opera浏览器只能采用filename*
+                // FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出
+                else if (browserAgent.contains("opera") || browserAgent.contains("mozilla")) {
+                    ret = "filename*=UTF-8''" + EncodedFileName;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    // 根据报表模板中的查询SQL获取报表数据
+    public static String BuildFromSelfSQL(gridreport.jni.Report report) throws Exception {
+        String DataText = "";
+
+        //从 XML 或 JSON 数据包中载入报表数据
+        String MasterQuerySQL = report.getQuerySQL();
+        String DetailQuerySQL = report.getDetailGrid() != null ? report.getDetailGrid().getRecordset().getQuerySQL() : null;
+        boolean MasterAssigned = (MasterQuerySQL != null && MasterQuerySQL.length() > 0);
+        boolean DetailAssigned = (DetailQuerySQL != null && DetailQuerySQL.length() > 0);
+        if (MasterAssigned || DetailAssigned) {
+            if (MasterAssigned && DetailAssigned) {
+                String MasterTableName = report.getXmlTableName();
+                if (MasterTableName == null || MasterTableName.length() == 0)
+                    MasterTableName = "Master";
+
+                String DetailTableName = report.getDetailGrid().getRecordset().getXmlTableName();
+                if (DetailTableName == null || DetailTableName.length() == 0)
+                    DetailTableName = "Detail";
+
+                ArrayList<ReportQueryItem> QueryItems = new ArrayList<ReportQueryItem>();
+                QueryItems.add(new ReportQueryItem(DetailQuerySQL, DetailTableName));
+                QueryItems.add(new ReportQueryItem(MasterQuerySQL, MasterTableName));
+                DataText = DataTextProvider.BuildList(QueryItems);
+            } else {
+                DataText = DataTextProvider.Build(MasterAssigned ? MasterQuerySQL : DetailQuerySQL);
+            }
+        }
+
+        return DataText;
+    }
+
+    // 将 ResultSet 的数据转储到报表的记明细录集中
+    public static void FillRecordToReport(gridreport.jni.Report report, ResultSet rs) throws Exception {
+        //建立ResultSet与报表记录集之间数据项的对应关系
+        //<<
+        class MatchFieldPair {
+            public gridreport.jni.Field rptField;
+            public gridreport.jni.FieldType rptFieldType;
+            public int rsColIndex;
+        }
+
+        ResultSetMetaData rsmd = rs.getMetaData();
+        int rsColCount = rsmd.getColumnCount();
+
+        gridreport.jni.Recordset rptRecordset = report.getDetailGrid().getRecordset();
+        gridreport.jni.Fields rptFields = rptRecordset.getFields();
+        int rptFldCount = rptFields.getCount();
+
+        MatchFieldPair[] matchFieldPairs = new MatchFieldPair[Math.min(rptFldCount, rsColCount)];
+
+        //根据字段名称与列名称进行匹配,建立ResultSet字段与Report记录集的字段之间的对应关系
+        int matchedCount = 0;
+        for (int i = 1; i <= rsColCount; ++i) {
+            String rsFieldName = rsmd.getColumnLabel(i);
+
+            for (int rptFldIndex = 1; rptFldIndex <= rptFldCount; ++rptFldIndex) {
+                gridreport.jni.Field rptField = rptFields.ItemAt(rptFldIndex);
+                String rptFieldName = rptField.getName();
+                if (rptField.getName().compareToIgnoreCase(rsFieldName) == 0) {
+                    MatchFieldPair pair = new MatchFieldPair();
+                    pair.rptField = rptField;
+                    pair.rptFieldType = rptField.getFieldType();
+                    pair.rsColIndex = i;
+
+                    matchFieldPairs[matchedCount] = pair;
+
+                    ++matchedCount;
+
+                    break;
+                }
+            }
+        }
+        //>>
+
+        //在推送记录数据前一定要调用 PrepareLoadData 方法
+        report.PrepareLoadData();
+
+        // 将 ResultSet 中的每一条记录转储到 Grid++report 的记录集中去
+        //rs.first(); //不需要,否则:Operation not allowed for a result set of type ResultSet.TYPE_FORWARD_ONLY.
+        while (rs.next()) {
+            if (!rptRecordset.Append())
+                throw new Exception("Report recordset can't append record!");
+
+            for (int i = 0; i < matchedCount; ++i) {
+                MatchFieldPair mfp = matchFieldPairs[i];
+                switch (mfp.rptFieldType) {
+                    case String:
+                        String strVal = rs.getString(mfp.rsColIndex);
+                        if (!rs.wasNull())
+                            mfp.rptField.setAsString(strVal);
+                        break;
+                    case Integer:
+                        int intVal = rs.getInt(mfp.rsColIndex);
+                        if (!rs.wasNull())
+                            mfp.rptField.setAsInteger(intVal);
+                        break;
+                    case Float:
+                    case Currency:
+                        double dblVal = rs.getDouble(mfp.rsColIndex);
+                        if (!rs.wasNull())
+                            mfp.rptField.setAsFloat(dblVal);
+                        break;
+                    case Boolean:
+                        Boolean boolVal = rs.getBoolean(mfp.rsColIndex);
+                        if (!rs.wasNull())
+                            mfp.rptField.setAsBoolean(boolVal);
+                        break;
+                    case DateTime:
+                        //TODO...
+                        break;
+                    case Binary:
+                        //Blob blobVal = rs.getBlob(mfp.rsColIndex);
+                        byte[] bytesVal = rs.getBytes(mfp.rsColIndex);
+                        if (!rs.wasNull())
+                            mfp.rptField.LoadFromMemory(bytesVal, bytesVal.length);
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            rptRecordset.Post();
+        }
+    }
+
+    //根据相对URL获取对应的完整文件路径名,参数为当前网页的相对文件路径名
+    public static String getRelativePath(HttpServletRequest request, String pathFile) //throws Exception
+    {
+        //如果第一个字符是“/”,则从根目录寻址,反之从当前网页所在目录相对寻址
+        String ret = "";
+        try {
+            ret = (pathFile.length() > 0) && pathFile.charAt(0) == '/' ?
+                    request.getRealPath(pathFile) :
+                    new java.io.File(request.getRealPath(request.getServletPath())).getParent() + "/" + pathFile;
+        } catch (Exception e) {
+        }
+        return ret;
+    }
+
+    //生成数据源连接串,根据当前运行环境与配置参数进行实际调整
+    public static String BuildMySQLConnectionString() {
+        //这里应该根据实际情况进行调整
+        return "MYSQL;Database=gridreport;User=root;";
+    }
+
+    public static int RGBToOleColor(byte r, byte g, byte b) {
+        return ((int) b) * 256 * 256 + ((int) g) * 256 + r;
+    }
+
+}