大型网站架构和优化策略和常见处理方案实例

大型在线商务网站的构架设计

本项目是一个在线商品交易平台,平台的主要目的是让企业在平台上发布商品及资讯,用户可以在此平台上购买商品并参与商品的评论。围绕这一目的,系统需要实现商品管理,商品订购,多部门订单处理,商品促销,内容管理等功能。

 

一、平台技术架构

 

使用EJB是因为当时3G已经出来,如果以后3G普及了,很多用户可能会通过手机去购物,这时候商城可定要提供两个终端,一种针对电脑,一种针对手机。

采用EJB的话可以把业务层的功能做成EJB部署到一台机器上面,再分别开发PC机的客户端和手机的客户端来远程调用EJB的业务层。

如果使用SSH架构的话,需要维护多份代码。

 

二、服务器架构

系统有多类服务器,分别为:数据库服务器、JBOSS应用服务器(运行EJB程序)、Tomcat服务器(EJB客户端)、Apache的http服务器(专门用来解析静态文件,减轻Tomcat压力)。关于Apache的http服务器与Tomcat的集成,请在网上搜一下。

 

随着访问量的增大,需要分别对这三类服务器进行配置集群。在集群环境下,需要加入负载均衡器(可以用硬件来做,也可以用软件来做,最好是用硬件来做。采用软件来做的一般是没钱的公司。)将用户的请求交给集群环境中的某一台机器来处理。

 

集群下文件的处理方式:1、通过软件方法,开一个socket,每台机器都传送一下,这种方式不大保险。2、硬件方法,磁盘阵列。效率很高,有些硬盘可以作为服务硬盘,有些可以作为备份硬盘。可以把数据根据raid方案放在不同的扇区里面,读取的时候,读取光头会同时读取。访问硬盘的速度可以成倍提高。

 

多台服务器间Session同步:如果集群环境下服务器不多(一般不超过5台,最好不超过3台。因为让每个Session都分布在各个服务器上会占用很大性能)可以通过Tomcat来配置集群的Session同步。如果服务器超过5台,最好单独搞一个Session服务器。

像这种集群方案,IBM这类公司已经提供了一套一系列的硬件方案,不过一套方案要卖个上百万。有四台WEB服务器,最少能同时支撑三万多人在线。一天的访问量就有三十来万。像当当网,一天也就十八万。

 

另一种方案是:COOKIE+DB来实现Session的功能,他会把数据存放到数据库,给用户写入个COOKIE,COOKIE存入随机生成唯一ID,标识用户身份,卓越网和当当网用的就是这种方案。

 

三、用到的几个性能优化的技术

OSCatch:缓存

Velocity:页面静态化

SSI:包含页面



大型电商网站,性能优化


问题:

1)当大型网站系统>10万人

一个小时内,会跟数据库交互10万次(国内有京东,淘宝),这就会出现数据库瓶颈,每个数据库最大连接数(socket)2000
在某一段短暂时间内1万人,会跟数据库发生1万次交互,2000-8000【30秒】 5000 3000
2000个用户很快就可以到页面
5000个用户访问页面比较慢
还有3000个用户会提示超时/服务器出现例外

这是访问性能的问题,原因是数据库瓶颈。

解决方案:
1>页面静态化
解决方案:使用模板技术(Velocity[9-10年]/Freemarket[5-6年])

2>2>缓存技术 (当数据更新比较快,几秒钟就更新一次,或者需要实时反映数据变化,或者页面具有很多种风格,不便于生成静态页面。如BBS)

    A.页面缓存(view,html代码)

缺点:不能做到实时更新,优点:比二级缓存效率更高。 
        在缓存的有效期内,显示的数据是没有变化的,只有当缓存过期以后才会有变化。
        页面缓存分为局部缓存和全局缓存,全局缓存是缓存整个页面的html代码,页面缓存是缓存页面中的某一块区域的html代码。
        缓存的范围:application范围(所有人都能共享,比如说产品列表显示)session(只针对某一个访问者,比如说缓存某个用户的个人信息)
        OSCache默认的范围是application范围,可以通过scope属性来修改。
        缓存默认的有效时间是3600秒,也就是一小时。可以通过time属性来修改。
        refresh属性h如果设置为true,则可以强行清楚缓存。
        key属性的作用:如果不设置key属性,就根据用户输入的url来做缓存,如果用户输入的url改变,缓存也会改变。oscatche会把所有的值放到一个map中,通过   key value来做判断。

<body>
             <oscache:cache key= "aaron"  scope= "session"  time= "15"  refresh= "${param.refresh }" >
                 <div>
                     <%= new  Date() %>
                 </div>
             </oscache:cache>
             <br>
             当前时间:<%= new  Date() %>
         </body>
         人为清除缓存<flush/>标签:
         <oscache:flush scope= "application"  />清除application范围内的所有缓存
         <oscache:flush scope= "session"  key= "foobar"  />清除session范围内的key为foobar的缓存
         <oscache:flush scope= "application"  group= "currencyData"  />清除application范围内组名为currencyData的所有缓存。
         使用oscache实现页面的全局缓存
         <filter>
             <filter-name>CacheFilter</filter-name>
             <filter- class >com.opensymphony.oscache.web.filter.CacheFilter</filter- class >
             <init-param>
                 <param-name>time</param-name>
                 <param-value> 7200 </param-value>
             </init-param>
             <init-param>
                 <param-name>scope</param-name>
                 <param-value>application</param-value>
             </init-param>
         </filter>
         <filter-mapping>
                 <filter-name>CacheFilter</filter-name>
                 <url-pattern>/product/list/*</url-pattern>
         </filter-mapping>

  

  缓存的key将以请求的uri+查询字符窜组成,如果你访问/oscache/index.jsp?name=tt和/oscache/index.jsp?name=ppp将得到两分缓存。
        缓存在初次访问页面时进行,后续的请求将会返回缓存中的内容。缓存中存放的内容为页面返回给用户的html代码。
        OSCache配置属性介绍
        cache.memory=true指定是否使用内存缓存,配置默认为true,即使用内存缓存。
        cache.capacity=1000指定缓存的容量,默认的容量是无限的,我们可以为他设置缓存数量。
        如果要使用硬盘缓存,我们可以这样设置:
        cache.memory=false
        cache.path=d:\\cache(指定缓存保存的路径,注意:路径应该采用双\\符号)
        cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener
        cache.persistence.class用于设计持久化类
        我们既要考虑页面的数量,也要考虑机器的内存,如果内存不多的话容易引起内存耗尽,系统的性能反而下降。
        但是最好还是用内存缓存,因为速度比较快,一般服务器的内存都大于10g,用于缓存产品列表页面够了。因为产品列表页面不会太多,假设我们有几万个的话,有一两个g就够了。
        CacheFilter的实现原理:

 

复制代码
 1 CacheFilter{
2 doFilter(request,response,chain){
3 String urlpath=req....;
4 if(oscache.contains(urlpath)){
5 String content=OsCache.getKey(urlpath);
6 response.write(content);
7 }else{
8 CacheHttpServletResponseWrapper wrapper=new CacheHttpServletResponseWrapper(response)
9 chain.doFilter(request,wrapper);
10 String content=wrapper.getContent();//获取服务器网客户端输出的html代码
11 OScache.put(urlpath,content);
12 response.write(content);
13 }
14 }
15
16 public CacheHttpServletResponseWrapper entends HttpServletResponseWrapper{
17 private String content;
18 public CacheHttpServletResponseWrapper(HttpServletResponse response){
19 ....
20 }
21 public void write(String content){
22 this.content=content;
23 }
24 public String getContent(){
25 return content;
26 }
27 }
28 }
复制代码


        页面缓存比二级缓存快的原因:当请求来之后系统就会交给过滤器,过滤器得到路径以后就会把缓存返回给客户端。
        二级缓存要经过action service 和jsp
    B.二级缓存(model/业务层,domain对象)优点:实时更新
    EHCache OSCache jbossCache(分布式缓存)
    第一步:导入ehcache的ehcache.jar文件(hibernate中有)
    第二部:在persistence.xml文件中添加下面配置项: 

1 <property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"/>
2 <property name="hibernate.cache.use_second_level_cache" value="true"/>
3 <property name="hibernate.cache.use_query_cache" value="false"/>

 第三步:在实体类上面标注@Cache(region="cn.aaron.bean.Person",usage=CacheConcurrencyStrategy.READ_WRITE)
    第四步:在classpath下面放入ehcache.xml,内容模板如下: 

 

复制代码
 1 <?xml version="1.0" encoding="UTF-8"?>
2 <ehcache>
3
4 <diskStore path="D:\cache" />
5
6 <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true"
7 timeToIdleSeconds="120"
8 timeToLiveSeconds="180"
9 diskPersistent="false"
10 diskExpiryThreadIntervalSeconds="60"/>
11
12 <cache name="cn.aaron.bean.Person" maxElementsInMemory="100"
13 eternal="false" overflowToDisk="true"
14 timeToIdleSeconds="300"
15 timeToLiveSeconds="600" diskPersistent="false" />
16 </ehcache>
复制代码

  注意<cache>节点中的name属性要和@Cache(region="cn.aaron.bean.Person",usage=CacheConcurrencyStrategy.READ_WRITE)中的region相同
    ehcache.xml文件中各项属性说明如下:
        defaultCache节点为缺省的缓存策略
        maxElementsInMemory 内存中最大允许的对象数量
        eternal 设置缓存中的对象是否永远不过期
        overflowToDisk 把溢出的对象放到硬盘上(对于本例而言,第1001个对象将存放在硬盘上)
        timeToIdleSeconds 指定缓存对象空闲多长时间会过期,过期的对象会被清除掉
        timeToLiveSeconds 指定缓存对象总的存活时间
        diskPersistent 当jvm结束时是否持久化对象
        diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
    
3>数据源 连接池里面放一些连接对象
每次都能跟数据库建立连接socket(client)----socket(数据库)

4>SSI 对性能提升不是那么明显(有一点点作用)
Server Side Include, 通常称为“服务器端包含”技术。
使用了SSI技术的文件默认的后缀名为.shtml,SSI技术通过在html文件中加入SSI指令让web服务器在输出标准HTML代码之前先解释SSI指令,
并把解释完成后的输出结果和HTML代码一起返回给客户端。
在大部分项目中,我们主要使用了SSI的包含指令<!-#include virtual="global/foot.jsp"-->,
他的作用类似于JSP中的<jsp:include page="/global/foot.jsp"/>标签。
使用SSI主要有如下两点优势:
1 SSI技术是通用技术,它不受限于运行环境,在java,.net,CGI,ASP,PHP下都可以使用SSI技术
2 解释SSI指令的效率比解释JSP的效率快很多,因为Servlet规范提供了太多的功能,这些功能都需要servlet引擎进行解释,所以效率比较低。
在目前,大部分的门户网站都是用SSI技术,解释SSI文件最佳的服务器是Apache HTTP Server。
大型门户网站基本都使用这个来解释SSI文件。
配置实用SSI
目前主流的web服务器都提供SSI实现,我们只需要打开SSI功能就可以使用。
tomcat也可以,但是并不会提高性能,因为使用的还是servlet引擎 .


电子商务网站分页的封装

首先创建一个QueryResult类来存放结果集和总记录数:package cn.wuxing.bean;

复制代码
 1 import java.util.List;
2
3 public class QueryResult<T> {
4 private List<T> resultlist;
5 private long totalrecord;
6
7 public List<T> getResultlist() {
8 return resultlist;
9 }
10 public void setResultlist(List<T> resultlist) {
11 this.resultlist = resultlist;
12 }
13 public long getTotalrecord() {
14 return totalrecord;
15 }
16 public void setTotalrecord(long totalrecord) {
17 this.totalrecord = totalrecord;
18 }
19 }
复制代码

接下来在DaoSupport类里面添加如下方法:

View Code

 

 getScrollData是最主要的方法,可以对分页进行排序、加条件等等。

 

  我们可以讲getScrollData得到的值存放到下面这个类中,请注意这个类的注释以及相关方法的注释:

 

 

复制代码
 1 import java.util.List;
2
3 /**
4 * 封装了Action向jsf所存放的一些数据
5 * 该类最重要的是调用setQueryResult方法,他会连环设置该类其他属性的值。
6 * @author bruce
7 * @param <T>
8 */
9 public class PageView<T> {
10 /** 分页数据 **/
11 private List<T> records;
12 /** 页码开始索引和结束索引 **/
13 private PageIndex pageindex;
14 /** 总页数 **/
15 private long totalpage = 1;
16 /** 每页显示记录数 **/
17 private int maxresult = 12;
18 /** 当前页 **/
19 private int currentpage = 1;
20 /** 总记录数 **/
21 private long totalrecord;
22 /** 页码数量 **/
23 private int pagecode = 10;
24 /** 要获取记录的开始索引 **/
25 public int getFirstResult() {
26 return (this.currentpage-1)*this.maxresult;
27 }
28 public int getPagecode() {
29 return pagecode;
30 }
31
32 public void setPagecode(int pagecode) {
33 this.pagecode = pagecode;
34 }
35
36 /**
37 * 构造方法,要使用这个类,必须传进每页显示记录数和当前页。
38 * @param maxresult
39 * @param currentpage
40 */
41 public PageView(int maxresult, int currentpage) {
42 this.maxresult = maxresult;
43 this.currentpage = currentpage;
44 }
45
46 /**
47 * 该方法存在一个连环的调用
48 * 将从数据库分页查询出来的总结果集和总记录数设置进来。
49 * 在设置总记录数的同时,会换算出总页数。详情参看setTotalrecord方法。
50 * 在设置总页数的同时,包含分页的开始索引和结束索引的PageIndex对象也确定了下来。详情参看setTotalpage方法。
51 * @param qr
52 */
53 public void setQueryResult(QueryResult<T> qr){
54 setRecords(qr.getResultlist());
55 setTotalrecord(qr.getTotalrecord());
56 }
57
58 public long getTotalrecord() {
59 return totalrecord;
60 }
61 /**
62 * 在设置总记录数的同时,会将总页数也设置进来。
63 * 在设置总页数的同时,包含分页的开始索引和结束索引的PageIndex对象也确定了下来。
64 * @param totalrecord
65 */
66 public void setTotalrecord(long totalrecord) {
67 this.totalrecord = totalrecord;
68 setTotalpage(this.totalrecord%this.maxresult==0? this.totalrecord/this.maxresult : this.totalrecord/this.maxresult+1);
69 }
70 public List<T> getRecords() {
71 return records;
72 }
73 public void setRecords(List<T> records) {
74 this.records = records;
75 }
76 public PageIndex getPageindex() {
77 return pageindex;
78 }
79 public long getTotalpage() {
80 return totalpage;
81 }
82 public void setTotalpage(long totalpage) {
83 this.totalpage = totalpage;
84 this.pageindex = PageIndex.getPageIndex(pagecode, currentpage, totalpage);
85 }
86 public int getMaxresult() {
87 return maxresult;
88 }
89 public int getCurrentpage() {
90 return currentpage;
91 }
92 }
复制代码

PageView用到了一个类PageIndex:

复制代码
 1 /**
2 * 此类有一个静态方法,返回带有分页的开始索引和结束索引值的对象
3 */
4 public class PageIndex {
5 private long startindex;
6 private long endindex;
7
8 public PageIndex(long startindex, long endindex) {
9 this.startindex = startindex;
10 this.endindex = endindex;
11 }
12 public long getStartindex() {
13 return startindex;
14 }
15 public void setStartindex(long startindex) {
16 this.startindex = startindex;
17 }
18 public long getEndindex() {
19 return endindex;
20 }
21 public void setEndindex(long endindex) {
22 this.endindex = endindex;
23 }
24
25 /**
26 * 静态方法来获取分页的开始索引和结束索引的值
27 * @param viewpagecount 分页栏总共显示多少个页码
28 * @param currentPage 当前页码值
29 * @param totalpage 总共页码值
30 * @return 带有开始索引和结束索引的页码对象
31 */
32 public static PageIndex getPageIndex(long viewpagecount, int currentPage, long totalpage){
33 long startpage = currentPage-(viewpagecount%2==0? viewpagecount/2-1 : viewpagecount/2);
34 long endpage = currentPage+viewpagecount/2;
35 if(startpage<1){
36 startpage = 1;
37 if(totalpage>=viewpagecount) endpage = viewpagecount;
38 else endpage = totalpage;
39 }
40 if(endpage>totalpage){
41 endpage = totalpage;
42 if((endpage-viewpagecount)>0) startpage = endpage-viewpagecount+1;
43 else startpage = 1;
44 }
45 return new PageIndex(startpage, endpage);
46 }
47 }
复制代码

前台jsp的封装如下:

复制代码
1 <%@ page language="java" pageEncoding="UTF-8"%>
2 <font color="#FFFFFF">
3 当前页:第${pageView.currentpage}页 | 总记录数:${pageView.totalrecord}条 | 每页显示:${pageView.maxresult}条 | 总页数:${pageView.totalpage}页</font> 
4 <c:forEach begin="${pageView.pageindex.startindex}" end="${pageView.pageindex.endindex}" var="wp">
5 <c:if test="${pageView.currentpage==wp}"><b><font color="#FFFFFF">第${wp}页</font></b></c:if>
6 <c:if test="${pageView.currentpage!=wp}"><a href="javascript:topage('${wp}')" class="a03">第${wp}页</a></c:if>
7 </c:forEach>
复制代码

倒数第二行用到了a href="javascript:topage('${wp}')"而不是直接写链接地址,这是因为这个jsp是封装好的,以后别的jsp调用就可以直接写一个topage的js方法来将链接地址写入。

 

大型网站对图片的下载,存放,及压缩管理

构建保存图片的路径:

1 String pathdir = "/images/product/"+ productTypeId+ "/"+ productId+ "/prototype";//构建文件保存的目录  

为什么要有那么多个目录,因为java本身不会去获取图片,而是调用了操作系统的一些接口来获取图片,如果一个目录下图片太多的话,操作系统获取图片的速度会变慢 ,所以巴巴运动网在构建图片路径的时候搞了多个目录,分散保存图片。

 

有了这个pathdir就可以得到图片保存目录的真实路径:

1 String realpathdir = request.getSession().getServletContext().getRealPath(pathdir);

获取了图片的真实路径后,就可以开始保存图片了: 

1 File savedir = new File(realpathdir);
2 File file = saveFile(savedir, filename, imagefile.getFileData());

imagefile为struts的FormFile类的对象,filename为文件名,这两个属性都可以从前台获取过来。以下是saveFile方法的代码: 

复制代码
 1 /**
2 * 保存文件
3 * @param savedir 存放目录
4 * @param fileName 文件名称
5 * @param data 保存的内容
6 * @return 保存的文件
7 * @throws Exception
8 */
9 public static File saveFile(File savedir, String fileName, byte[] data) throws Exception{
10 if(!savedir.exists()) savedir.mkdirs();//如果目录不存在就创建
11 File file = new File(savedir, fileName);
12 FileOutputStream fileoutstream = new FileOutputStream(file);
13 fileoutstream.write(data);
14 fileoutstream.close();
15 return file;
16 }
复制代码

保存完图片后还要保存一张图片的缩略图,宽度为140px 

 

复制代码
1 String pathdir140 = "/images/product/"+ productTypeId+ "/"+ productId+ "/140x";//140宽度的图片保存目录
2 String realpathdir140 = request.getSession().getServletContext().getRealPath(pathdir140);
3 File savedir140 = new File(realpathdir140);
4 if(!savedir140.exists()) savedir140.mkdirs();//如果目录不存在就创建
5 File file140 = new File(realpathdir140, filename);
6 ImageSizer.resize(file, file140, 140, ext);
复制代码

这里我们用到了一个从网上下的用于压缩图片的ImageSizer工具类的静态方法,resize方法传进去的四个参数分别代表原始图片对象,需要被压缩的图片对象,压缩宽度的大小,图片后缀名。这个工具类只能压缩jpg, png, gif(非动画)三种格式,如果想压缩更多的格式需要付费。以下是该工具类: 

 

复制代码
  1 /**
2 * 图像压缩工具
3 * @author lihuoming@sohu.com
4 *
5 */
6 public class ImageSizer {
7 public static final MediaTracker tracker = new MediaTracker(new Component() {
8 private static final long serialVersionUID = 1234162663955668507L;}
9 );
10 /**
11 * @param originalFile 原图像
12 * @param resizedFile 压缩后的图像
13 * @param width 图像宽
14 * @param format 图片格式 jpg, png, gif(非动画)
15 * @throws IOException
16 */
17 public static void resize(File originalFile, File resizedFile, int width, String format) throws IOException {
18 if(format!=null && "gif".equals(format.toLowerCase())){
19 resize(originalFile, resizedFile, width, 1);
20 return;
21 }
22 FileInputStream fis = new FileInputStream(originalFile);
23 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
24 int readLength = -1;
25 int bufferSize = 1024;
26 byte bytes[] = new byte[bufferSize];
27 while ((readLength = fis.read(bytes, 0, bufferSize)) != -1) {
28 byteStream.write(bytes, 0, readLength);
29 }
30 byte[] in = byteStream.toByteArray();
31 fis.close();
32 byteStream.close();
33
34 Image inputImage = Toolkit.getDefaultToolkit().createImage( in );
35 waitForImage( inputImage );
36 int imageWidth = inputImage.getWidth( null );
37 if ( imageWidth < 1 )
38 throw new IllegalArgumentException( "image width " + imageWidth + " is out of range" );
39 int imageHeight = inputImage.getHeight( null );
40 if ( imageHeight < 1 )
41 throw new IllegalArgumentException( "image height " + imageHeight + " is out of range" );
42
43 // Create output image.
44 int height = -1;
45 double scaleW = (double) imageWidth / (double) width;
46 double scaleY = (double) imageHeight / (double) height;
47 if (scaleW >= 0 && scaleY >=0) {
48 if (scaleW > scaleY) {
49 height = -1;
50 } else {
51 width = -1;
52 }
53 }
54 Image outputImage = inputImage.getScaledInstance( width, height, java.awt.Image.SCALE_DEFAULT);
55 checkImage( outputImage );
56 encode(new FileOutputStream(resizedFile), outputImage, format);
57 }
58
59 /** Checks the given image for valid width and height. */
60 private static void checkImage( Image image ) {
61 waitForImage( image );
62 int imageWidth = image.getWidth( null );
63 if ( imageWidth < 1 )
64 throw new IllegalArgumentException( "image width " + imageWidth + " is out of range" );
65 int imageHeight = image.getHeight( null );
66 if ( imageHeight < 1 )
67 throw new IllegalArgumentException( "image height " + imageHeight + " is out of range" );
68 }
69
70 /** Waits for given image to load. Use before querying image height/width/colors. */
71 private static void waitForImage( Image image ) {
72 try {
73 tracker.addImage( image, 0 );
74 tracker.waitForID( 0 );
75 tracker.removeImage(image, 0);
76 } catch( InterruptedException e ) { e.printStackTrace(); }
77 }
78
79 /** Encodes the given image at the given quality to the output stream. */
80 private static void encode( OutputStream outputStream, Image outputImage, String format )
81 throws java.io.IOException {
82 int outputWidth = outputImage.getWidth( null );
83 if ( outputWidth < 1 )
84 throw new IllegalArgumentException( "output image width " + outputWidth + " is out of range" );
85 int outputHeight = outputImage.getHeight( null );
86 if ( outputHeight < 1 )
87 throw new IllegalArgumentException( "output image height " + outputHeight + " is out of range" );
88
89 // Get a buffered image from the image.
90 BufferedImage bi = new BufferedImage( outputWidth, outputHeight,
91 BufferedImage.TYPE_INT_RGB );
92 Graphics2D biContext = bi.createGraphics();
93 biContext.drawImage( outputImage, 0, 0, null );
94 ImageIO.write(bi, format, outputStream);
95 outputStream.flush();
96 }
97
98 /**
99 * 缩放gif图片
100 * @param originalFile 原图片
101 * @param resizedFile 缩放后的图片
102 * @param newWidth 宽度
103 * @param quality 缩放比例 (等比例)
104 * @throws IOException
105 */
106 private static void resize(File originalFile, File resizedFile, int newWidth, float quality) throws IOException {
107 if (quality < 0 || quality > 1) {
108 throw new IllegalArgumentException("Quality has to be between 0 and 1");
109 }
110 ImageIcon ii = new ImageIcon(originalFile.getCanonicalPath());
111 Image i = ii.getImage();
112 Image resizedImage = null;
113 int iWidth = i.getWidth(null);
114 int iHeight = i.getHeight(null);
115 if (iWidth > iHeight) {
116 resizedImage = i.getScaledInstance(newWidth, (newWidth * iHeight) / iWidth, Image.SCALE_SMOOTH);
117 } else {
118 resizedImage = i.getScaledInstance((newWidth * iWidth) / iHeight, newWidth, Image.SCALE_SMOOTH);
119 }
120 // This code ensures that all the pixels in the image are loaded.
121 Image temp = new ImageIcon(resizedImage).getImage();
122 // Create the buffered image.
123 BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null), temp.getHeight(null),
124 BufferedImage.TYPE_INT_RGB);
125 // Copy image to buffered image.
126 Graphics g = bufferedImage.createGraphics();
127 // Clear background and paint the image.
128 g.setColor(Color.white);
129 g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null));
130 g.drawImage(temp, 0, 0, null);
131 g.dispose();
132 // Soften.
133 float softenFactor = 0.05f;
134 float[] softenArray = {0, softenFactor, 0, softenFactor, 1-(softenFactor*4), softenFactor, 0, softenFactor, 0};
135 Kernel kernel = new Kernel(3, 3, softenArray);
136 ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
137 bufferedImage = cOp.filter(bufferedImage, null);
138 // Write the jpeg to a file.
139 FileOutputStream out = new FileOutputStream(resizedFile);
140 // Encodes image as a JPEG data stream
141 JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
142 JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bufferedImage);
143 param.setQuality(quality, true);
144 encoder.setJPEGEncodeParam(param);
145 encoder.encode(bufferedImage);
146 }
147 }
复制代码

允许用户上传文件,那么我们一定要注意安全,如果用户上传了一个jsp文件,而这个文件的上传路径恰好能被用户访问到,那么用户可能会在这个jsp文件里面做一个对网站其他文件的文件操作,可以将文件保存到web-inf下面,如果用户需要下载,我们就写一个servlet读取这个文件,以流的方式返回给用户。


在线交易系统的购物车的实现


在购物车中,我们可以删除购物项,修改产品的购买数量,清空购物车,进入结算中心。

以下是购物车的代码

复制代码
  1 /**
2 * 购物车
3 */
4 public class BuyCart {
5 /* 购物项 */
6 private List<BuyItem> items = new ArrayList<BuyItem>();
7 /* 配送信息 */
8 private OrderDeliverInfo deliverInfo;
9 /* 购买者联系信息 */
10 private OrderContactInfo contactInfo;
11 /* 支付方式 */
12 private PaymentWay paymentWay;
13 /* 购买者与收货人是否相同 */
14 private Boolean buyerIsrecipients;
15 /* 配送费 */
16 private float deliveFee = 10f;
17 /* 附言 */
18 private String note;
19
20 public String getNote() {
21 return note;
22 }
23
24 public void setNote(String note) {
25 this.note = note;
26 }
27
28 public float getDeliveFee() {
29 return deliveFee;
30 }
31
32 public void setDeliveFee(float deliveFee) {
33 this.deliveFee = deliveFee;
34 }
35
36 public PaymentWay getPaymentWay() {
37 return paymentWay;
38 }
39
40 public void setPaymentWay(PaymentWay paymentWay) {
41 this.paymentWay = paymentWay;
42 }
43
44 public Boolean getBuyerIsrecipients() {
45 return buyerIsrecipients;
46 }
47
48 public void setBuyerIsrecipients(Boolean buyerIsrecipients) {
49 this.buyerIsrecipients = buyerIsrecipients;
50 }
51
52 public OrderContactInfo getContactInfo() {
53 return contactInfo;
54 }
55
56 public void setContactInfo(OrderContactInfo contactInfo) {
57 this.contactInfo = contactInfo;
58 }
59
60 public OrderDeliverInfo getDeliverInfo() {
61 return deliverInfo;
62 }
63
64 public void setDeliverInfo(OrderDeliverInfo deliverInfo) {
65 this.deliverInfo = deliverInfo;
66 }
67
68 public List<BuyItem> getItems() {
69 return items;
70 }
71
72 /**
73 * 添加购物项
74 * @param item 购物项
75 */
76 public void add(BuyItem item){
77 if(this.items.contains(item)){
78 for(BuyItem it : this.items){
79 if(it.equals(item)){
80 it.setAmount(it.getAmount()+1);
81 break;
82 }
83 }
84 }else{
85 this.items.add(item);
86 }
87 }
88 /**
89 * 删除指定购物项
90 * @param item 购物项
91 */
92 public void delete(BuyItem item){
93 if(this.items.contains(item)) this.items.remove(item);
94 }
95 /**
96 * 清空购物项
97 */
98 public void deleteAll(){
99 this.items.clear();
100 }
101
102 /**
103 * 计算商品总销售价
104 * @return
105 */
106 public float getTotalSellPrice(){
107 float totalprice = 0F;
108 for(BuyItem item : this.items){
109 totalprice += item.getProduct().getSellprice() * item.getAmount();
110 }
111 return totalprice;
112 }
113 /**
114 * 计算商品总市场价
115 * @return
116 */
117 public float getTotalMarketPrice(){
118 float totalprice = 0F;
119 for(BuyItem item : this.items){
120 totalprice += item.getProduct().getMarketprice() * item.getAmount();
121 }
122 return totalprice;
123 }
124 /**
125 * 计算总节省金额
126 * @return
127 */
128 public float getTotalSavedPrice(){
129 return this.getTotalMarketPrice() - this.getTotalSellPrice();
130 }
131 /**
132 * 计算订单的总费用
133 * @return
134 */
135 public float getOrderTotalPrice(){
136 return this.getTotalSellPrice()+ this.deliveFee;
137 }
138 }
复制代码

 以下是购物项的代码:



在线商品系统的最近浏览商品列表  --- Cookie实现

实现方式 Cookie 


1.对最近浏览商品的实现需要用到cookie,一下代码可以获取cookie:

复制代码
 1 /**
2 * 获取cookie的值
3 * @param request
4 * @param name cookie的名称
5 * @return
6 */
7 public static String getCookieByName(HttpServletRequest request, String name) {
8 Map<String, Cookie> cookieMap = WebUtil.readCookieMap(request);
9 if(cookieMap.containsKey(name)){
10 Cookie cookie = (Cookie)cookieMap.get(name);
11 return cookie.getValue();
12 }else{
13 return null;
14 }
15 }
16
17 protected static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
18 Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
19 Cookie[] cookies = request.getCookies();
20 if (null != cookies) {
21 for (int i = 0; i < cookies.length; i++) {
22 cookieMap.put(cookies[i].getName(), cookies[i]);
23 }
24 }
25 return cookieMap;
26 }
复制代码

 

 readCookieMap先获取request里面的所有cookie,然后以键值对的形式存放到一个Map中。在getCookieByName方法中,先调用readCookieMap方法来获取所有的cookie,然后在查询自己想要查找的那个cookie的值。

2.完成了获取cookie值之后,我们就可以对cookie里面的浏览记录进行修改排序:

复制代码
 1 public String buildViewHistory(HttpServletRequest request, Integer currentProductId){
2 //23-2-6-5
3 //1.如果当前浏览的id已经在浏览历史里了,我们要把移到最前面
4 //2.如果浏览历史里已经达到了10个产品了,我们需要把最选进入的元素删除
5 String cookieValue = WebUtil.getCookieByName(request, "productViewHistory");
6 LinkedList<Integer> productids = new LinkedList<Integer>();
7 if(cookieValue!=null && !"".equals(cookieValue.trim())){
8 String[] ids = cookieValue.split("-");
9 for(String id : ids){
10 productids.offer(new Integer(id.trim()));
11 }
12 if(productids.contains(currentProductId)) productids.remove(currentProductId);
13 if(productids.size()>=10) productids.poll();
14 }
15 productids.offer(currentProductId);
16 StringBuffer out = new StringBuffer();
17 for(Integer id : productids){
18 out.append(id).append('-');
19 }
20 out.deleteCharAt(out.length()-1);
21 return out.toString();
22 }
复制代码

为了方便排序 cookie的值是:用户浏览过的商品的id加横杠分隔符结合而成的。如:23-2-6-5。

3.排序结束后我们又可以将新的cookie存入浏览器中:

复制代码
 1 /**
2 * 添加cookie
3 * @param response
4 * @param name cookie的名称
5 * @param value cookie的值
6 * @param maxAge cookie存放的时间(以秒为单位,假如存放三天,即3*24*60*60; 如果值为0,cookie将随浏览器关闭而清除)
7 */
8 public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
9 Cookie cookie = new Cookie(name, value);
10 cookie.setPath("/");
11 if (maxAge>0) cookie.setMaxAge(maxAge);
12 response.addCookie(cookie);
13 }
复制代码

 该方法的调用如下:



在线购物系统权限模块


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值