項目概述
此電商項目為本人學習項目,后端 使用nginx實現負載均衡轉發請求到多台tomcat服務器,使用多台 redis服務器分布式 緩存用戶登錄信息。
項目已經部署到阿里雲服務器,從阿里雲linux服務器租用,到項目前后台代碼的完善,前后花費了3個月左右的時間。
項目地址
本人已經部署該項目,租用的阿里雲服務器的ip地址為:47.106.172.105,購買的域名地址為:www.xwld.site,
商城地址為:http://www.xwld.site
大部分商品詳情圖片還沒有上傳,暫時只上傳了一個商品用於演示。
地址:http://www.xwld.site/list.html?categoryId=100006
后端所用技術
-
Spring
-
SpringMVC
-
MyBatis
-
MySQL
-
Lombok:省去手動創建setter和getter方法
-
Mycat:數據庫分庫分表中間件
-
Redis:緩存
-
Jedis:Redis的Java Client
-
Nginx
-
Tomcat
-
Maven
-
第三方接口
-
支付寶沙箱測試接口,實現訂單支付
-
前端所用技術
-
Html
-
Css
-
JavaScript
-
Node.js
-
Npm
-
Webpack
-
Charles
項目架構及功能模塊圖
linux項目運行的shell腳本
首先從碼雲中拉取項目對應的tag,然后進入項目目錄,執行maven打包命令。
[root@izwz918nqae9soh0p70seuz bin]# cat mall_backend.sh #!/bin/bash # author xw # create_date 2018年11月6日 "===========進入git項目mmall目錄=============" cd /app/gitRepository/mmall_backend echo "==================刪除之前的tag=====================" rm -rf * echo "==========git切換分之到mmall-v1.0===============" git clone --branch back_release_$1 git@gitee.com:xwmall/backend.git echo "===========編譯並跳過單元測試====================" cd backend/mmallv4.0 mvn clean package -Pprod -Dmaven.test.skip=true echo "============刪除舊的ROOT.war===================" rm -rf /soft/tomcat1/webapps/ROOT.war echo "======拷貝編譯出來的war包到tomcat下-ROOT.war=======" cp /app/gitRepository/mmall_backend/backend/mmallv4.0/target/mmall.war /soft/tomcat1/webapps/ROOT.war echo "============刪除tomcat下舊的ROOT文件夾=============" rm -rf /soft/tomcat1/webapps/ROOT echo "====================關閉tomcat=====================" #/soft/tomcat1/bin/shutdown.sh pkill -9 java echo "================sleep 10s=========================" for i in {1..10} do echo $i"s" sleep 1s done echo "====================啟動tomcat=====================" /soft/tomcat1/bin/startup.sh
github與 碼雲
碼雲
此項目本人使用碼雲來存儲項目
每發布一個版本創建一個tag標記,shell 中使用git命令獲取相應的tag版本
github
github上面是目前是沒什么項目,
由於github上面創建私有的項目需要收費,故而,一直使用碼雲來存儲項目,等到后期項目再進一步完善,再遷移到github中開源。
github地址如下:https://github.com/weiqinshian/mall
項目完整購買流程展示
首頁
商品列表頁面
暫時只上傳了一個商品。
地址:http://www.xwld.site/list.html?categoryId=100006
商品詳情頁面
地址:http://www.xwld.site/detail.html?productId=27
登錄頁面
測試賬號:admin
密碼:123456
購物車頁面
訂單確認頁面
生成支付二維碼頁面
本人手機安裝了沙箱測試版支付寶,使用沙箱測試版支付寶掃碼支付。
手機掃碼支付成功之后
查看訂單詳情
redis 分布式緩存session
項目中集成redis客戶端Jedis
jedis 是redis 的java客戶端連接包。
百度搜索【maven】進入,maven中央倉庫,搜索jedis 獲取到,jedis-client的maven引用。
如,下圖:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.0</version> </dependency> |
注意版本號一定不要出錯,否則,可能會和其他jar 有沖突。
redis連接池的構建及調試
要設置為靜態類型,是需要保證在tomcat啟動的時候就將jedis 連接池加載進來。
Jedis API封裝及調試
jedis 是 redis官方推薦的java 客戶端,使用java去連接redis的時候,引入jedis相關jar包就可以了。
使用jedis去連接redis ,要手寫redis 連接池配置,在項目啟動的時候,要初始化連接池。
redis 連接工具類
本示例中展示了兩個redis的連接配置,可以執行下面類中mian進行測試,連接兩台redis服務。
執行main方法前,先要在阿里雲服務器,通過配置文件啟動兩台端口配置不同的redis服務。
package com.mmall.common; import java.util.ArrayList; import java.util.List; import com.mmall.util.PropertiesUtil; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.ShardedJedis; import redis.clients.jedis.ShardedJedisPool; import redis.clients.util.Hashing; import redis.clients.util.Sharded; /** * redis 分布式,連接池配置 * @author XW * */ public class RedisShardedPool { private static ShardedJedisPool pool;//sharded(分片) jedis連接池 private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20")); //最大連接數 private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle狀態(空閑的)的jedis實例的個數 private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle狀態(空閑的)的jedis實例的個數 private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一個jedis實例的時候,是否要進行驗證操作,如果賦值true。則得到的jedis實例肯定是可以用的。 private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));//在return一個jedis實例的時候,是否要進行驗證操作,如果賦值true。則放回jedispool的jedis實例肯定是可以用的。
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip"); private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port")); private static String redis1Pwd = PropertiesUtil.getProperty("redis1.pwd");
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip"); private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port")); private static String redis2Pwd = PropertiesUtil.getProperty("redis2.pwd");
private static void initPool(){ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); config.setTestOnBorrow(testOnBorrow); config.setTestOnReturn(testOnReturn); config.setBlockWhenExhausted(true);//連接耗盡的時候,是否阻塞,false會拋出異常,true阻塞直到超時。默認為true。 JedisShardInfo info1 = new JedisShardInfo(redis1Ip,redis1Port,1000*2); info1.setPassword(redis1Pwd); JedisShardInfo info2 = new JedisShardInfo(redis2Ip,redis2Port,1000*2); info2.setPassword(redis2Pwd);//設置redis登錄密碼 List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>(2); jedisShardInfoList.add(info1); jedisShardInfoList.add(info2); /** * Hashing.MURMUR_HASH,使用一致性hash算法 */ pool = new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN); }
static{ initPool(); }
public static ShardedJedis getJedis(){ return pool.getResource(); }
public static void returnBrokenResource(ShardedJedis jedis){ pool.returnBrokenResource(jedis); } public static void returnResource(ShardedJedis jedis){ pool.returnResource(jedis); } public static void main(String[] args) { ShardedJedis jedis = pool.getResource(); for(int i =0;i<10;i++){ jedis.set("key"+i,"value"+i); } returnResource(jedis); System.out.println("program is end"); } } |
pool = new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
配置redis 連接池的時候,可以指定 分布式算法 使用:一致性hash算法
Jackson 封裝JsonUtil及測試
以前,在用戶登錄之后,是將一個user對象,存入session中。
Redis 中不能存儲對象,故而,需要將user 對象,先序列化為一個string,然后,將這個string 存入 redis中。
將相關序列化,和反序列化的方法,封裝在JsonUtil類中。
JsonUtil類代碼
可以運行此類中的main方法,進行測試。
package com.mmall.util; import java.text.SimpleDateFormat; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion; import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.type.TypeReference;
import com.mmall.pojo.TestPojo;
import lombok.extern.slf4j.Slf4j;
/** * Created by XW */ @Slf4j public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper(); static{ /*1.下面這些參數,會影響對象序列化的行為*/ //對象的所有字段全部序列化為字符串,不管是否有字段為null objectMapper.setSerializationInclusion(Inclusion.ALWAYS); //取消默認轉換timestamps形式,帶時間戳 objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); //忽略空Bean轉json的錯誤,當user對象轉json時,user對象中所有屬性為null,轉化為json對象也不報異常 objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); //所有的日期格式都統一為以下的樣式,即yyyy-MM-dd HH:mm:ss,實體類user中有屬性為Date類型,使用此設置能格式化這種類型的格式。 objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); /*2.下面這些參數,會影響對象反序列化的行為*/ //忽略在json字符串中存在,但是在java對象中不存在對應屬性的情況。防止轉換拋出錯誤 objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); }
/** * 對象轉換為字符串 * @param obj * @return */ public static <T> String obj2String(T obj){ if(obj == null){ return null; } try { return obj instanceof String ? (String)obj : objectMapper.writeValueAsString(obj); } catch (Exception e) { log.warn("Parse Object to String error",e); return null; } }
/** * 返回格式化好的字符串 * @param obj * @return */ public static <T> String obj2StringPretty(T obj){ if(obj == null){ return null; } try { return obj instanceof String ? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (Exception e) { log.warn("Parse Object to String error",e); return null; } }
/** * 反序列化方法 * 將字符串轉換為T類型對象 * 注意:此方法不能反序列化,集合+泛型類型的字符串,如: List<User> * @param str * @param clazz * @return */ public static <T> T string2Obj(String str,Class<T> clazz){ if(StringUtils.isEmpty(str) || clazz == null){ return null; } try { return clazz.equals(String.class)? (T)str : objectMapper.readValue(str,clazz); } catch (Exception e) { log.warn("Parse String to Object error",e); return null; } } /** * 反序列化方法 * 將字符串轉換為T類型對象 * 注意:此方法比上一個強大,能反序列化,集合+泛型類型的字符串,如: List<User> * List<User> userListObj1 = JsonUtil.string2Obj(userListStr, new TypeReference<List<User>>() * @param str * @param typeReference * @return */ public static <T> T string2Obj(String str, TypeReference<T> typeReference){ if(StringUtils.isEmpty(str) || typeReference == null){ return null; } try { return (T)(typeReference.getType().equals(String.class)? str : objectMapper.readValue(str,typeReference)); } catch (Exception e) { log.warn("Parse String to Object error",e); return null; } }
/** * 反序列化方法 * 將字符串轉換為T類型對象 * 注意:此方法比上一個強大,能反序列化,集合+泛型類型的字符串,如: List<User> * List<User> userListObj2 = JsonUtil.string2Obj(userListStr,List.class,User.class); * @param str * @param collectionClass * @param elementClasses * @return */ public static <T> T string2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){ JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); try { return objectMapper.readValue(str,javaType); } catch (Exception e) { log.warn("Parse String to Object error",e); return null; } }
|