• Mall电商项目总结(一)——项目概述


    项目概述

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

    登录页面

    地址:http://www.xwld.site/user-login.html?redirect=http%3A%2F%2Fwww.xwld.site%2Forder-detail.html%3ForderNo%3D1549504659738

    测试账号: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。则放回jedispooljedis实例肯定是可以用的。

    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);

    //忽略空Beanjson的错误,当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;

    }

    }

  • 相关阅读:
    批量更新sql |批量update sql
    智力测试题3
    【管理心得之二十一】管得少就是管得好
    查看sqlserver被锁的表以及如何解锁
    AD域相关的属性和C#操作AD域
    毕业5年小结一下
    WPF版公司的自动签到程序
    用友畅捷通高级前端笔试题(一)凭借回忆写出
    .NET中制做对象的副本(三)通过序列化和反序列化为复杂对象制作副本
    .NET中制做对象的副本(二)继承对象之间的数据拷贝
  • 原文地址:https://www.cnblogs.com/weiqinshian/p/10361599.html
Copyright © 2020-2023  润新知