项目概述
此电商项目为本人学习项目,后端 使用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; } }
|