1 创建应用的时候需要手机短信验证码,记得在手机号前面加上86.
2 安装google eclipse插件,最好是通过market安装,安装过程巨慢,需要耐心
3 由于dns被墙,需要绑定host方可访问:http://big-orange.appsp0t.com
只需ping 出www.google.cn的ip进行绑定即可
203.208.46.242 big-orange.appspot.com
203.208.46.242 picasaweb.google.com
203.208.46.242 docs.google.com
4 文件上传可以考虑存储在gae默认提供的datastore的blob字段中。(独立一台作为文件服务器)
参考 http://www.ibm.com/developerworks/cn/opensource/os-cn-gaeflupl/
http://www.uml.org.cn/sjjm/201204054.asp(good)
今天尝试spring mvc版的文件上传功能,很不容易啊。spring mvc自带的文件上传请求解析器依赖于apache common fileupload,而fileupload做解析的时候用到了rmi。rmi是gae环境下禁用的类。所以这边需要自定义的文件上传解析器,刚好有个开源项目可以满足需求 https://github.com/pjesi/springextras,需要引入其中的两个类
@RequestMapping(value = "/add.htm", method = RequestMethod.POST) public void addImage(HttpServletRequest request, String path1, String path2) { // 转型为MultipartHttpRequest(重点的所在) MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; // 获得第1张图片(根据前台的name名称得到上传的文件) MultipartFile imgFile1 = multipartRequest.getFile("picUrl"); try { String url = picasaManager.uploadPic(imgFile1.getBytes()); ItemDO itemDO = new ItemDO(); itemDO.setId(Long.valueOf(request.getParameter("id"))); itemDO.setTitle(request.getParameter("title")); itemDO.setPrice(Double.valueOf(request.getParameter("price"))); itemDO.setPicUrl(url); itemManager.addItemDO(itemDO); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
spring mvc需要配置为
<?xml version="1.0" encoding="UTF-8"?> <beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Scans within the base package of the application for @Components to configure as beans --> <!-- @Controller, @Service, @Configuration, etc. --> <context:component-scan base-package="com.orange.control" /> <!-- Enables the Spring MVC @Controller programming model --> <mvc:annotation-driven /> <bean id="multipartResolver" class="is.hax.spring.web.multipart.StreamingMultipartResolver" /> <import resource="biz-context.xml"/> </beans>
5 静态文件,和样式文件,可以考虑放到另外一台gae应用上。(独立一台作为静态文件服务器)
6 图片可以考虑使用https://picasaweb.google.com免费图片空间,或者baidu的http://hi.baidu.com/hill007299/albumcreate/multipicture/album
参考 http://jerryllx.iteye.com/blog/444057
http://www.guan8.net/Java/517220.html
实践证明用picasa是靠谱的
代码
public class DefaultPicasaManager implements PicasaManager { String username = "XXXX@126.com";// 用户名 String passwd = "XXX"; // 密码 String albumid = "5751935385548943857"; // 相簿ID PicasawebService picasawebService = new PicasawebService("orange"); String albumPostUrl = "http://picasaweb.google.com/data/feed/api/user/" + username + "/albumid/" + albumid; public DefaultPicasaManager() { try { picasawebService.setUserCredentials(username, passwd); } catch (AuthenticationException e) { e.printStackTrace(); } } @Override public String uploadPic(byte[] bytes) { try { PhotoEntry photoEntry = createPhotoEntry(bytes); // 上传图片 PhotoEntry returnedPhoto = picasawebService.insert(new URL(albumPostUrl), photoEntry); MediaContent content1 = (MediaContent) returnedPhoto.getContent(); return content1.getUri(); } catch (Exception e) { e.printStackTrace(); } return null; } private PhotoEntry createPhotoEntry(byte[] mediaBytes) { PhotoEntry photoEntry = new PhotoEntry(); photoEntry.setTitle(new PlainTextConstruct("orange")); photoEntry.setDescription(new PlainTextConstruct("orange")); photoEntry.setClient("big-orange"); MediaContent content = new MediaContent(); String mediaType = "image/jpeg"; MediaSource v = new MediaByteArraySource(mediaBytes, mediaType); content.setMediaSource(v); photoEntry.setContent(content); return photoEntry; } }
打印图片链接http://lh4.ggpht.com/-8rpAseX3DrU/T9MBqQCn7YI/AAAAAAAAABU/PicJHg4sea8/longji.jpg
一个账号有1G的免费空间,可以考虑多申请几个账号,图片随机存储在不同账号上。
通过这个可以拿到相册id(albumid)
http://picasaweb.google.com/data/feed/api/user/hill007299@126.com?kind=album
(使用http://code.google.com/apis/gdata/ ,因为它依赖google的核心库,需要同时下载http://code.google.com/p/guava-libraries/,然后把jar导入eclipse)
7 全文检索功能
最新版本的已经提供
http://www.caijixia.net/xingyezixun/2012052551449.html
https://developers.google.com/appengine/docs/java/search/overview
public class DefaultSearchManager implements SearchManager { /** * The index used by this application. Since we only have one index we * create one instance only. We build an index with the default consistency, * which is Consistency.PER_DOCUMENT. These types of indexes are most * suitable for streams and feeds, and can cope with a high rate of updates. */ private static final Index INDEX = SearchServiceFactory.getSearchService().getIndex( IndexSpec.newBuilder().setName("shared_index")); @Override public boolean indexItem(ItemDO itemDO) { try { int rating = 1; Document.Builder docBuilder = Document.newBuilder() .addField(Field.newBuilder().setName("title").setText(itemDO.getTitle())) .addField(Field.newBuilder().setName("price").setNumber(itemDO.getPrice())) .addField(Field.newBuilder().setName("picUrl").setText(itemDO.getPicUrl())) .addField(Field.newBuilder().setName("id").setNumber(itemDO.getId())) .addField(Field.newBuilder().setName("rating").setNumber(rating)); Document doc = docBuilder.build(); INDEX.add(doc); return true; } catch (Exception e) { e.printStackTrace(); return false; } } @Override public List<ItemDO> queryItemDOs(String key) { int limit = 10; List<Document> found = new ArrayList<Document>(); try { // Rather than just using a query we build a search request. // This allows us to specify other attributes, such as the // number of documents to be returned by search. Query query = Query.newBuilder().setOptions(QueryOptions.newBuilder().setLimit(limit).build()).build(key); Results<ScoredDocument> results = INDEX.search(query); return convertToItemDO(results); } catch (Exception e) { return null; } } private List<ItemDO> convertToItemDO(Results<ScoredDocument> results) { List<ItemDO> itemDOs = new ArrayList<ItemDO>(); for (ScoredDocument scoredDoc : results) { ItemDO tempItemDO = new ItemDO(); tempItemDO.setTitle(scoredDoc.getOnlyField("title").getText()); tempItemDO.setPicUrl(scoredDoc.getOnlyField("picUrl").getText()); tempItemDO.setPrice(scoredDoc.getOnlyField("price").getNumber()); tempItemDO.setId(scoredDoc.getOnlyField("id").getNumber().longValue()); itemDOs.add(tempItemDO); } return itemDOs; } }
8 缓存使用memcache。
public class DefaultCacheService implements CacheService { MemcacheService memcache = MemcacheServiceFactory.getMemcacheService("orange"); @Override public Object get(String key) { Object r = null; try { r = memcache.get(key); } catch (Exception e) { e.printStackTrace(); } return r; } @Override public boolean delete(String key) { memcache.delete(key); return true; } @Override public boolean put(String key, Serializable o, int exp) { try { if (exp > 0) { memcache.put(key, o, Expiration.byDeltaSeconds(exp)); } else { memcache.put(key, o); } } catch (Exception e) { e.printStackTrace(); } return true; } }
9 持久层服务使用datastore,基于big-table
public class DefaultItemManager implements ItemManager { PersistenceManager persistenceManager = JDOHelper.getPersistenceManagerFactory("transactions-optional") .getPersistenceManager(); CacheService cacheService; String prefix = "ITEM_"; @Override public ItemDO getItemDOById(long id) { try { String key = prefix + id; ItemDO itemDO = (ItemDO) cacheService.get(key); if (itemDO != null) { return itemDO; } itemDO = persistenceManager.getObjectById(ItemDO.class, id); if (itemDO != null) { cacheService.put(key, itemDO, 1000); } return itemDO; } catch (Exception e) { e.printStackTrace(); return null; } } @Override public boolean addItemDO(ItemDO itemDO) { persistenceManager.makePersistent(itemDO); String key = prefix + itemDO.getId(); cacheService.delete(key); return true; } @Override public List<ItemDO> getItemDOsByQuery() { return null; } public void setCacheService(CacheService cacheService) { this.cacheService = cacheService; } }