i++

2020-05-14 10:36

@JFinal 您好!感谢您的及时回复。我们用的Cache是com.jfinal.plugin.redis.Cache。不是net.sf.ehcache.Cache。一个是redis的。一个是ehcache的。两个东西。 如jfinal官方文档中:Cache bbsCache = Redis.use("bbs");这样用的。

i++

2020-05-14 07:51

@JFinal 可以继承,但我们公司好多项目用了jfinal,然后好多代码里引用了Cache这个类。现在要加集群功能,如果用继承的方式,那些有引用到Cache的类都要换成新的Cache类。所有功能代码都要重新测试,评审。真是个灾难啊。

i++

2018-12-28 15:31

修正后的方法:
public int[] batchSave(String tableName, List recordList, int batchSize) {
if (recordList == null || recordList.size() == 0)
return new int[0];

Record record = recordList.get(0);
Map cols = record.getColumns();
int index = 0;
StringBuilder columns = new StringBuilder();
String pKeys = "";//增加的
// the same as the iterator in Dialect.forDbSave() to ensure the order of the columns
for (Entry e : cols.entrySet()) {
if (config.dialect.isOracle()) { // 支持 oracle 自增主键
Object value = e.getValue();
if (value instanceof String && ((String)value).endsWith(".nextval")) {
pKeys = pKeys.concat(",").concat(e.getKey());//增加的
continue ;
}
}

if (index++ > 0) {
columns.append(',');
}
columns.append(e.getKey());
}
if(pKeys.startsWith(",")){
pKeys.replaceFirst(",", "");
}
StringBuilder sql = new StringBuilder();
String[] pKeysNoUse = pKeys.split(",");//修改的
List parasNoUse = new ArrayList();
config.dialect.forDbSave(tableName, pKeysNoUse, record, sql, parasNoUse);
return batch(sql.toString(), columns.toString(), recordList, batchSize);
}

i++

2018-12-28 09:20

@似水流言1 getSqlPara()方法在jfinal的Db类里啊

i++

2018-12-27 18:30

@JFinal
你好
public int[] batchSave(String tableName, List recordList, int batchSize)
oracle 自增主键时这个方法还是不能用。
3.6版的也是。
这个方法中把主键从columns删除的同时,应该把主键赋值给pKeysNoUse

否则在config.dialect.forDbSave(tableName, pKeysNoUse, record, sql, parasNoUse);方法中isPrimaryKey(colName, pKeys) 是false的。
会把主键拼成?

i++

2018-08-24 12:59

@JFinal 用合并方法2时也有userSubscribeList.removeAll(sameList); 这行代码。但是没有问题。sameList是从userSubscribeList得来的,所以可以remove。没有什么问题。

i++

2018-06-09 20:27

@JFinal 发现个“问题",不知道算不算BUG。如果捕获了Db.tx(() -> {});代码段的异常,在catch里又不把异常抛出去。发生异常时事务是不会回滚的。代码如下:
try{
Db.tx(() -> {
这里发生异常
return false
});
} catch(){这里不再抛异常}

i++

2017-10-19 18:08

以下为自定义FileRender代码。只能重写整个类。就为了不打印个异常。建议下个版本把这个异常去掉。


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jfinal.kit.LogKit;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.StrKit;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;
import com.jfinal.render.RenderManager;


/**
* 描述:自定义FileRender.为了不打印org.apache.catalina.connector.ClientAbortException: 远程主机强迫关闭了一个现有的连接。
* @author Robin Zhang
* @created 2017年10月19日 下午6:00:02
*/
public class FileRender extends Render {

private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

private File file;
private static String baseDownloadPath;
private static ServletContext servletContext;
private String downloadFileName = null;

public FileRender(File file) {
if (file == null) {
throw new IllegalArgumentException("file can not be null.");
}
this.file = file;
init();
}

public FileRender(File file, String downloadFileName) {
this(file);

if (StrKit.isBlank(downloadFileName)) {
throw new IllegalArgumentException("downloadFileName can not be blank.");
}
this.downloadFileName = downloadFileName;
init();
}

public FileRender(String fileName) {
if (StrKit.isBlank(fileName)) {
throw new IllegalArgumentException("fileName can not be blank.");
}

String fullFileName;
fileName = fileName.trim();
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
if (baseDownloadPath.equals("/")) {
fullFileName = fileName;
} else {
fullFileName = baseDownloadPath + fileName;
}
} else {
fullFileName = baseDownloadPath + File.separator + fileName;
}

this.file = new File(fullFileName);
init();
}

public FileRender(String fileName, String downloadFileName) {
this(fileName);

if (StrKit.isBlank(downloadFileName)) {
throw new IllegalArgumentException("downloadFileName can not be blank.");
}
this.downloadFileName = downloadFileName;
init();
}

private void init() {
String downloadPath = RenderManager.me().getConstants().getBaseDownloadPath();
downloadPath = downloadPath.trim();
downloadPath = downloadPath.replaceAll("\\\\", "/");

String baseDownloadPath;
// 如果为绝对路径则直接使用,否则把 downloadPath 参数作为项目根路径的相对路径
if (PathKit.isAbsolutelyPath(downloadPath)) {
baseDownloadPath = downloadPath;
} else {
baseDownloadPath = PathKit.getWebRootPath() + File.separator + downloadPath;
}

// remove "/" postfix
if (baseDownloadPath.equals("/") == false) {
if (baseDownloadPath.endsWith("/")) {
baseDownloadPath = baseDownloadPath.substring(0, baseDownloadPath.length() - 1);
}
}
FileRender.baseDownloadPath = baseDownloadPath;
FileRender.servletContext = RenderManager.me().getServletContext();
}

public void render() {
if (file == null || !file.isFile()) {
RenderManager.me().getRenderFactory().getErrorRender(404).setContext(request, response).render();
return;
}

// ---------
response.setHeader("Accept-Ranges", "bytes");
String fn = downloadFileName == null ? file.getName() : downloadFileName;
response.setHeader("Content-disposition", "attachment; " + encodeFileName(request, fn));
String contentType = servletContext.getMimeType(file.getName());
response.setContentType(contentType != null ? contentType : DEFAULT_CONTENT_TYPE);

// ---------
if (StrKit.isBlank(request.getHeader("Range"))) {
normalRender();
} else {
rangeRender();
}
}

protected String encodeFileName(String fileName) {
try {
// return new String(fileName.getBytes("GBK"), "ISO8859-1");
return new String(fileName.getBytes(getEncoding()), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
return fileName;
}
}

/**
* 依据浏览器判断编码规则
*/
public String encodeFileName(HttpServletRequest request, String fileName) {
String userAgent = request.getHeader("User-Agent");
try {
String encodedFileName = URLEncoder.encode(fileName, "UTF8");
// 如果没有UA,则默认使用IE的方式进行编码
if (userAgent == null) {
return "filename=\"" + encodedFileName + "\"";
}

userAgent = userAgent.toLowerCase();
// IE浏览器,只能采用URLEncoder编码
if (userAgent.indexOf("msie") != -1) {
return "filename=\"" + encodedFileName + "\"";
}

// Opera浏览器只能采用filename*
if (userAgent.indexOf("opera") != -1) {
return "filename*=UTF-8''" + encodedFileName;
}

// Safari浏览器,只能采用ISO编码的中文输出,Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出
if (userAgent.indexOf("safari") != -1 || userAgent.indexOf("applewebkit") != -1
|| userAgent.indexOf("chrome") != -1) {
return "filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO8859-1") + "\"";
}

// FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出
if (userAgent.indexOf("mozilla") != -1) {
return "filename*=UTF-8''" + encodedFileName;
}

return "filename=\"" + encodedFileName + "\"";
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

private void normalRender() {
response.setHeader("Content-Length", String.valueOf(file.length()));
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
for (int len = -1; (len = inputStream.read(buffer)) != -1;) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
} catch (IOException e) {
if (getDevMode()) {
throw new RenderException(e);
}
} catch (Exception e) {
throw new RenderException(e);
} finally {
if (inputStream != null)
try {
inputStream.close();
} catch (IOException e) {
LogKit.error(e.getMessage(), e);
}
if (outputStream != null)
try {
outputStream.close();
} catch (IOException e) {
if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
// LogKit.error(e.getMessage());
} else {
LogKit.error(e.getMessage(), e);
}
}
}
}

private void rangeRender() {
Long[] range = { null, null };
processRange(range);

String contentLength = String.valueOf(range[1].longValue() - range[0].longValue() + 1);
response.setHeader("Content-Length", contentLength);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // status =
// 206

// Content-Range: bytes 0-499/10000
StringBuilder contentRange = new StringBuilder("bytes ").append(String.valueOf(range[0])).append("-")
.append(String.valueOf(range[1])).append("/").append(String.valueOf(file.length()));
response.setHeader("Content-Range", contentRange.toString());

InputStream inputStream = null;
OutputStream outputStream = null;
try {
long start = range[0];
long end = range[1];
inputStream = new BufferedInputStream(new FileInputStream(file));
if (inputStream.skip(start) != start)
throw new RuntimeException("File skip error");
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
long position = start;
for (int len; position <= end && (len = inputStream.read(buffer)) != -1;) {
if (position + len <= end) {
outputStream.write(buffer, 0, len);
position += len;
} else {
for (int i = 0; i < len && position <= end; i++) {
outputStream.write(buffer[i]);
position++;
}
}
}
outputStream.flush();
} catch (IOException e) {
if (getDevMode())
throw new RenderException(e);
} catch (Exception e) {
throw new RenderException(e);
} finally {
if (inputStream != null)
try {
inputStream.close();
} catch (IOException e) {
LogKit.error(e.getMessage(), e);
}
if (outputStream != null)
try {
outputStream.close();
} catch (IOException e) {
if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
// LogKit.error(e.getMessage());
} else {
LogKit.error(e.getMessage(), e);
}
}
}
}

/**
* Examples of byte-ranges-specifier values (assuming an entity-body of
* length 10000): The first 500 bytes (byte offsets 0-499, inclusive):
* bytes=0-499 The second 500 bytes (byte offsets 500-999, inclusive):
* bytes=500-999 The final 500 bytes (byte offsets 9500-9999, inclusive):
* bytes=-500 Or bytes=9500-
*/
private void processRange(Long[] range) {
String rangeStr = request.getHeader("Range");
int index = rangeStr.indexOf(',');
if (index != -1)
rangeStr = rangeStr.substring(0, index);
rangeStr = rangeStr.replace("bytes=", "");

String[] arr = rangeStr.split("-", 2);
if (arr.length < 2)
throw new RuntimeException("Range error");

long fileLength = file.length();
for (int i = 0; i < range.length; i++) {
if (StrKit.notBlank(arr[i])) {
range[i] = Long.parseLong(arr[i].trim());
if (range[i] >= fileLength)
range[i] = fileLength - 1;
}
}

// Range format like: 9500-
if (range[0] != null && range[1] == null) {
range[1] = fileLength - 1;
}
// Range format like: -500
else if (range[0] == null && range[1] != null) {
range[0] = fileLength - range[1];
range[1] = fileLength - 1;
}

// check final range
if (range[0] == null || range[1] == null || range[0].longValue() > range[1].longValue())
throw new RuntimeException("Range error");
}
}

i++

2017-10-19 18:02

@JFinal
FileRender中的代码:

} finally {
if (inputStream != null)
try {inputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
if (outputStream != null)
try {outputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
}

186行使用LogKit.error打印了,没有向外面抛,所以在自定义FileRender中无法try做处理。我试过自己实现super.render()功能。但。。。servletContext为null.因为RenderManager中写死的FileRender对象,所以要在自己的FileRender去取servletContext...

i++

2017-08-26 16:31

@JFinal 其实我自己不是用SqlKit.SQL_PARA_KEY 我自己用自己的常量了。只是发出来时改成这个了。没注意看这个不是public的。我改下