文档章节

使用SSM+Solr优雅的实现电商项目中的搜索功能

T
 TyCoding
发布于 2018/09/25 20:08
字数 4239
阅读 424
收藏 27

在学习了Redis&Spring-Data-Redis入门Solr&Spring-Data-Solr入门后,接下来就该是项目实战了。这次我们用Vue.JS和ElementUI写前端页面,优雅的整合SSM-Shiro-Redis-Solr框架。

手摸手教你优雅的实现电商项目中的Solr搜索功能,整合SSM框架和Shiro安全框架;教你用Vue.JS和ElementUI写出超漂亮的页面

项目开源地址:SSM整合Solr实现电商项目中的搜索功能

<!--more-->

<br/>

准备

Shiro

关于Shiro,我这里写了详细的SSM框架整合Shiro安全框架的文档,利用SSM框架+Shiro框架实现用户-角色-权限管理系统;

详细的文档地址:SSM整合Shiro框架后的开发

项目Github源码地址:SSM整合Shiro框架后的开发, 欢迎star

<br/>

Solr & Spring-Data-Solr

** 关于Solr安装配置和Spring-Data-Solr的入门Demo请查看博文:Solr和Spring-Data-Solr的入门学习 **

Solr需要单独部署到Tomcat服务器上,我这里提供自己已经安装和配置好的Tomcat和Solr: Github

注意事项:

  1. 部署Solr的Tomcat端口和本地项目的端口不能相同,会冲突。

  2. 注意Github仓库solr-tomcat/webapps/solr/WEB-INF/web.xml中solrhome的位置要修改为自己的。

<br/>

Redis & Spring-Data-Redis

关于Redis安装配置和Spring-Data-Redis的入门Demo请查看博文:Redis和Spring-Data-Redis的入门学习

<br/>

起步

启动Solr和Redis

如果访问localhost:8080/solr/index.html出现Solr Admin页则启动成功。

<br/>

初始化表结构

CREATE DATABASE ssm_redis DEFAULT CHARACTER SET utf8;

具体的数据库约束文件和表数据请看:ssm-redis-solr/db

这里我们模拟添加了934条商品数据

<br/>

搭建SSM-Shiro集成环境

具体的SSM整合Shiro的教程请看我这个项目: 手摸手教你SSM整合Shiro。这里不再说SSM整合Shiro的教程,默认认为已经大家已经完成。

<br/>

搭建SSM-Redis集成环境

集成SSM-Redis开发环境,首先你需要安装Redis并启动Redis-Server,然后就是在项目中搭建Spring-Data-Redis的运行环境:

<br/>

创建redis-config.properties配置文件

redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=true

创建spring-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:other/*.properties"/>
    <!-- redis 相关配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!-- 连接时最大的等待时间(毫秒) -->
        <property name="maxWaitMillis" value="${redis.maxWait}"/>
        <!-- 在提取一个jedis实例时,是否提前进行验证操作;如果为true,则得到的jedis实例均是可用的 -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.pass}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <!-- 序列化策略 推荐使用StringRedisSerializer -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
    </beans>
</beans>

配置文件我们已经在博文Redis&Spring-Data-Redis入门中介绍过了,其中最需要关注的就是hashKeySerializer序列化配置。

如果你不添加序列化配置也是没影响的,但是存入Redis数据库中的KEY和VALUE值都是乱码的,当然是用Spring-Data-Redis是毫无影响的。

如果添加了序列化配置:配置值类型数据用StringRedisSerializer序列化方式;配置Hash类型数据用JdkSerializationRedisSerializer序列化方式。

<br/>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring/spring*.xml"})
public class TestRedisTemplate {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void setValue(){
        redisTemplate.boundValueOps("name").set("tycoding");
    }
}

然后在redis-cli命令行窗口中输入get name就能得到VALUE为:tycoding。

至此,SSM集成Redis已经完成。

<br/>

搭建SSM-Solr集成环境

创建spring-solr.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:solr="http://www.springframework.org/schema/data/solr"
       xsi:schemaLocation="http://www.springframework.org/schema/data/solr
  		http://www.springframework.org/schema/data/solr/spring-solr-1.0.xsd
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- solr服务器地址 -->
    <solr:solr-server id="solrServer" url="http://127.0.0.1:8080/solr/new_core"/>
    <!-- solr模板,使用solr模板可对索引库进行CRUD的操作 -->
    <bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate">
        <constructor-arg ref="solrServer"/>
    </bean>
</beans>

spring-solr配置文件我们再博文Solr&Spring-Data-Solr入门中我们已经介绍过了。其中最需要注意的就是solr服务器url地址的配置,组成结构一定要是:Ip + 端口 + solr项目名称 + core实例名称

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-solr.xml")
public class TestSolrTemplate {
    @Autowired
    private SolrTemplate solrTemplate;
    @Test
    public void testAdd() {
        Goods goods = new Goods(1L, "IPhone SE", "120", "手机", "Apple", "Apple");
        solrTemplate.saveBean(goods);
        solrTemplate.commit(); //提交
    }
}

关于Goods实体类定义,请看/java/cn/tycoding/entity/Goods.java 然后我们在浏览器中访问localhost:8080/solr/index.html,点击Query可以看到:

<br/>

开始

数据库数据批量导入Solr索引库

上面已经完成了SSM-Shiro-Redis-Solr的集成环境配置,那么思考:既然用Solr完成搜索功能,那么怎么实现呢?

以前我们直接请求数据库用concat()模糊查询实现搜索功能,但是这种方式有很大的弊端:1.给数据库造成的访问压力很大;2.无法识别用户查询的数据到底是title字段还是price字段... 如果用Solr完成搜索功能,就很容易解决了这些问题。那么我们需要往Solr索引库中添加数据,Sorl才能搜索出来数据呀:

@Component
public class SolrUtil {
    @Autowired
    private GoodsMapper goodsMapper;
    @Autowired
    private SolrTemplate solrTemplate;

    /**
     * 实现将数据库中的数据批量导入到Solr索引库中
     */
    public void importGoodsData() {
        List<Goods> list = goodsMapper.findAll();
        System.out.println("====商品列表====");
        for (Goods goods : list) {
            System.out.println(goods.getTitle());
        }
        solrTemplate.saveBeans(list);
        solrTemplate.commit(); //提交
        System.out.println("====结束====");
    }
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring*.xml");
        SolrUtil solrUtil = (SolrUtil) context.getBean("solrUtil");
        solrUtil.importGoodsData();
    }
}

对应的Mapper.xml

<select id="findAll" resultType="cn.tycoding.entity.Goods">
    SELECT * FROM tb_item
</select>

目的就是查询数据库中所有数据,然后调用solrTemplate.saveBean(List)将查询到的List集合数据添加到Solr索引库中,之后我们在localhost:8080/solr/index.html中能查询出来共有934条记录数据。

<br/>

实现Solr搜索功能

前端

由于前端使用了Vue.JSElementUI,如果不了解的话请仔细阅读官方文档,或者,你可以查看我的博文记录: Vue学习学习笔记

index.html中定义:

<el-input placeholder="请输入内容" type="text" class="input-with-select" @keyup.enter.native="search" v-model="searchMap.keywords">
    <el-button slot="append" icon="el-icon-search" @click="search"></el-button>
</el-input>

index.js中定义:

data() {
  return {
    searchMap: { keywords: '' }
  }
},
methods: {
  search() {
      this.$http.post('goods/search.do', this.searchMap).then(result => {
          this.goods = result.body.rows;
      });
  },
}

后端

GoodsController.java中定义:

@RequestMapping("/search")
public Map<String, Object> search(@RequestBody Map<String, Object> searchMap) {
    return goodsService.search(searchMap);
}

为什么要用Map接收前端数据?

如果前端传来的数据不止一个,且不属于后端的任何一个实体类对象,前端传来的仅是一个自定义的对象(xx:{});那么后端势必不能通过实体类对象来接收,且是POST请求,后端必须也使用对象类型来接收数据且必须用@RequestBody标识对象,原因:

  1. 前端传来的数据不止一种。
  2. 前端传来的数据封装在对象中。

所以,综上,Map<K, V>这种数据结构最适合作为接收对象类型。

<br/>

GoodsServiceImpl.xml中定义:

public Map<String, Object> search(Map searchMap) {
  Map<String, Object> map = new HashMap<String, Object>();
  Query query = new SimpleQuery();
  //添加查询条件
  Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
  query.addCriteria(criteria);
  ScoredPage<Goods> page = solrTemplate.queryForPage(query, Goods.class);
  map.put("rows", page.getContent()); //返回查询到的数据
  return map;
}

启动项目,再搜索框中输入苹果回车即出现:

但是发现页面中只显示10条数据(实际我们添加的苹果手机数据不止10条),为了更优雅的显示,我们实现分页

<br/>

实现分页查询

在后端中我们用limit方式显示分页,或者更简单的用Mybatis的PageHelper分页查询实现分页查询。 而,我们在博文Solr&Spring-Data-Solr入门学习中讲了Solr分页查询的方式:

修改GoodsServiceImpl.java

public Map<String, Object> search(Map searchMap) {
    Map<String, Object> map = new HashMap<String, Object>();
    Query query = new SimpleQuery();
    //添加查询条件
    Criteria criteria = new Criteria("item_keywords");
    if (searchMap.get("keywords") != null && searchMap.get("keywords") != ""){
        System.out.println("执行了...");
        criteria.is(searchMap.get("keywords"));
    }
    query.addCriteria(criteria);

    //分页查询
    Integer pageCode = (Integer) searchMap.get("pageCode");
    if (pageCode == null) {
        pageCode = 1; //默认第一页
    }
    Integer pageSize = (Integer) searchMap.get("pageSize");
    if (pageSize == null) {
        pageSize = 18; //默认18
    }
    query.setOffset((pageCode - 1) * pageSize); //从第几条记录开始查询:= 当前页 * 每页的记录数
    query.setRows(pageSize);
    ScoredPage<Goods> page = solrTemplate.queryForPage(query, Goods.class);
    map.put("rows", page.getContent()); //返回查询到的数据
    map.put("totalPage", page.getTotalPages()); //返回总页数
    map.put("total", page.getTotalElements()); //返回总记录数
    return map;
}
前端

ElementUI提供了分页查询的插件,我们仅需要传给它几个参数,即可实现分页,详细的文档介绍请看我这篇博文:Vue+ElementUI实现分页

index.html中添加:

<el-pagination
        background
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="pageConf.pageCode"
        :page-sizes="pageConf.pageOption"
        :page-size="pageConf.pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="pageConf.totalPage">
</el-pagination>

index.js中添加:

data() {
  return {
    searchMap: {
        keywords: '',

        //分页选项
        pageCode: '', //当前页
        pageSize: '', //每页的记录数
    }
    pageConf: {
        //设置一些初始值(会被覆盖)
        pageCode: 1, //当前页
        pageSize: 18, //每页显示的记录数
        totalPage: 20, //总记录数
        pageOption: [18, 25, 30], //分页选项
    },
  }
},
methods: {
    //pageSize改变时触发的函数
    handleSizeChange(val) {
        console.log(val);
        this.searchMap.pageSize = val;
        this.searchMap.pageCode = this.pageConf.pageCode;
        this.search(this.pageConf.pageCode, val);
    },
    //当前页改变时触发的函数
    handleCurrentChange(val) {
        console.log(val);
        this.searchMap.pageCode = val;
        this.searchMap.pageSize = this.pageConf.pageSize;
        this.search(val, this.searchMap.pageSize);
    },
}

我们只需要修改以上JavaScript代码即可,因为search方法传给后台的是searchMap这个对象数据,只要其中包含了pageCodepageSize这两个参数,就会被封装到后端接收的Map集合中。 其中点击上一页,下一页按钮触发的函数在<el-pagination>中已经定义了,如果点击上一页下一页触发handleCurrentChange()函数,如果点击每页5条记录变成10条记录就会触发handleSizeChange()函数;两个函数都又调用了search()方法,当点击分页按钮时就发送数据给后端,其中包含当前点击的pageCodepageSize的值,后端接收到这两个值进行分页计算,并将数据返回给前端。

<br/>

实现条件过滤

上面讲了分页查询,仅仅是单方面的查询,并没有任何条件限制。这一功能在SSM中我们直接通过concat()模糊条件过滤,但在Solr中提供了FilterQuery()实现条件过滤查询:

修改GoodsServiceImpl.java文件,在search()方法中添加:

      if (searchMap.get("category") != null) {
          if (!searchMap.get("category").equals("")) {
              System.out.println("执行了category");
              FilterQuery filterQuery = new SimpleFilterQuery();
              Criteria filterCriteria = new Criteria("item_category").is(searchMap.get("category"));
              filterQuery.addCriteria(filterCriteria);
              query.addFilterQuery(filterQuery);
          }
      }

      //按品牌过滤
      if (searchMap.get("brand") != null) {
          if (!searchMap.get("brand").equals("")) {
              System.out.println("执行了brand...");
              FilterQuery filterQuery = new SimpleFilterQuery();
              Criteria filterCriteria = new Criteria("item_brand").is(searchMap.get("brand"));
              filterQuery.addCriteria(filterCriteria);
              query.addFilterQuery(filterQuery);
          }
      }
前端

修改index.html

<el-checkbox-group :max="1" v-model="change.category" @change="selectMethod">
    <el-checkbox v-for="classify in classifyData.category" :label="classify" border size="mini">{{classify}}</el-checkbox>
</el-checkbox-group>
<el-checkbox-group :max="1" v-model="change.brand" @change="selectMethod">
    <el-checkbox v-for="classify in classifyData.brand" :label="classify" border size="mini">{{classify}}</el-checkbox>
</el-checkbox-group>

修改index.js

searchMap: {
  brand: '',
  price: '',
}

//checkbox选择的选项
change: {
    category: [],
    brand: [],
    price: []
},

selectMethod(val) {
    this.searchMap.category = this.change.category[0];
    this.searchMap.brand = this.change.brand[0];
    this.search(); //每次点击后都进行查询
},

<br/>

实现商品价格升降过滤

修改GoodsServiceImpl.java中的search方法:

  //按价格的升降序查询
  if (searchMap.get("sort") != null) {
      if (!searchMap.get("sort").equals("")) {
          String sortValue = (String) searchMap.get("sort");
          String sortField = (String) searchMap.get("field");
          if (sortValue != null && !sortValue.equals("")) {
              if (sortValue.equals("asc")) {
                  Sort sort = new Sort(Sort.Direction.ASC, "item_" + sortField);
                  query.addSort(sort);
              }
              if (sortValue.equals("desc")) {
                  Sort sort = new Sort(Sort.Direction.DESC, "item_" + sortField);
                  query.addSort(sort);
              }
          }
      }
  }
前端

修改index.html

<el-checkbox-group v-model="change.price" :max="1" @change="selectMethod">
        <el-checkbox :label="'0-500'" border size="mini">0-500元</el-checkbox>
        <el-checkbox :label="'500-1000'" border size="mini">500-1000元</el-checkbox>
        ...
</el-checkbox-group>

修改index.js

searchMap: {
  price: '',
}

selectMethod(val) {
  this.searchMap.price = this.change.sort[0];
}

<br/>

实现查询结果高亮显示

实现高亮显示,我们先看一下效果:

可看到根据关键字苹果查询出来的记录中苹果字样都被标记为红色斜体样式,这即是我们的目的,你可以查看淘宝的查询,也是查询字样以红色显示出来。

如果使用高亮查询,那么之前的Query query = new SimpleQuery()就不在满足需求了;高亮查询使用HighlightQuery query = new SimpleHighlightQuery();

修改GoodsServiceImpl.java中的Search()方法:

    HighlightQuery query = new SimpleHighlightQuery();
    HighlightOptions highlightOptions = new HighlightOptions().addField("item_title"); //设置高亮的域
    highlightOptions.setSimplePrefix("<em style='color: red'>"); //设置高亮前缀
    highlightOptions.setSimplePostfix("</em>"); //设置高亮后缀
    query.setHighlightOptions(highlightOptions); //设置高亮选项

初始化高亮查询HighlightQuery类,利用其包含的setSimplePerfix设置查询结果的前缀html标签,利用setSimplePostfix设置查询结果后缀HTML标签。

循环高亮查询结果集合数据:

    HighlightPage<Goods> page = solrTemplate.queryForHighlightPage(query, Goods.class);
    //循环高亮入口集合
    for (HighlightEntry<Goods> h : page.getHighlighted()) {
        Goods goods = h.getEntity(); //获取原实体类
        if (h.getHighlights().size() > 0 && h.getHighlights().get(0).getSnipplets().size() > 0) {
            goods.setTitle(h.getHighlights().get(0).getSnipplets().get(0)); //设置高亮的结果
        }
    }

之前我们可以直接通过query.getContent()获取到查询结果,但是使用了高亮查询HighlightQuery就不能直接通过getContent()获取数据了,我们要手动遍历HighlightPage这个对象,他是一个层层嵌套的集合数据,详细数据结构这里不再说了。

<br/>

更新索引

既然使用了Solr管理商品数据,查询时直接从Solr索引库中查询数据,而不是查询数据库中的数据,那么如果修改了商品的信息:添加、删除、修改。那么就必须同步使Solr索引库中的数据和数据库中的数据保持一致才能实现查询出来的数据是真实的。

所以我们要在对商品添加、删除、修改的时候同步更新Solr索引库数据:

修改GoodsServiceImpl.javacreatedeleteupdate方法,更新Solr索引库:

    @Override
    public void create(Goods goods) {
        goodsMapper.create(goods);

        Long id = goodsMapper.maxId(); //获取根据主键自增插入的最新一条记录的ID值
        goods.setId(id);

        //更新索引库
        solrTemplate.saveBean(goods);
        solrTemplate.commit();
    }

    @Override
    public void update(Goods goods) {
        goodsMapper.update(goods);

        //更新索引库
        solrTemplate.deleteById(String.valueOf(goods.getId()));
        solrTemplate.commit();
        List<Goods> list = new ArrayList<Goods>();
        list.add(goods);
        solrTemplate.saveBeans(list);
        solrTemplate.commit();
    }

    @Override
    public void delete(Long... ids) {
        for (Long id : ids) {
            goodsMapper.delete(id);

            //更新索引库
            solrTemplate.deleteById(String.valueOf(id));
            solrTemplate.commit();
        }
    }

注意,在添加商品时,传来的实体类中并不包含Id属性数据,因为我们使用了MySql的自增主键;而想要向Solr索引库中添加新数据又必须制定Id属性值,因为Solr不会自增主键呀。所以我们在goodsMapper.create()后调用MaxId()方法获取最新自增的主键值,其在Mapper.xml中定义:

<select id="maxId" resultType="Long">
    SELECT MAX(id) FROM tb_item
</select>

<br/>

关于Redis

眼看着教程就结束了,但是为什么教程中没有解释Redis的应用呢?

解释

关于SSM整合Redis的教程请仔细看我的博文:Redis和Spring-Data-Redis入门学习

因为Redis在本项目中并没有用到实际的应用中,为何?首先我们要考虑为什么要用Redis?

Redis缓存嘛,不就是缓存哪些大批量的数据减轻服务器压力的。但是,并不是说所有的大批量的数据都得去缓存,虽然我们项目中的商品管理功能中已经出现了900多条数据,但是这些数据都需要频繁的正删改,而缓存技术中一个难点就是缓存同步问题,你的缓存数据必须时刻和数据库(真实的数据)保持一致,如果每修改一条记录就去更新一遍缓存势必会给缓存服务器造成很大压力。

什么数据适合放进缓存?不常修改的数据。比如商品的分类列表数据:

但是原谅我,我只是想简单的实现以下Solr的搜索功能,真实项目中商品的分类数据肯定是存在另一张表中,由于我并没有实现,所以Redis在本项目中的应用就比较少了。

<br/>

项目截图

<br/>

交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!

<br/>

联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

<br/>

交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!

<br/>

联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

© 著作权归作者所有

T
粉丝 21
博文 23
码字总数 68904
作品 0
安阳
私信 提问
找java驻场开发工程师做电商网站项目开发(景程-北京)

需要多名满足本需求所述的能力要求的中级开发工程师驻场开发。 【工作职责】 1、基于Web的电商网站项目开发,开发语言为Java,数据库为Sqlserver2010,数据库服务器和应用服务器操作系统均为...

jingchengtengda
2016/06/12
35
8
Hybris电商平台搜索服务实践

电商平台搜索服务特点 随着电商平台的快速发展和所销售商品的数量大规模增长,从大量的商品数据中快速获取用户关注的商品,变得越来越有挑战性。优秀电商平台能够吸引客户的因素之一,就是拥...

dev_csdn
2018/05/02
0
0
电商搜索引擎的架构设计和性能优化

「 OneAPM 技术公开课」由应用性能管理第一品牌 OneAPM 发起,内容面向 IT 开发和运维人员。云集技术牛人、知名架构师、实践专家共同探讨技术热点。本文系「OneAPM 技术公开课」第一期演讲嘉...

OneAPM蓝海讯通
2015/10/30
273
0
WeGeek直播课:从0到1快速开发电商小程序(文字版)

作者:钟鑫 源码示例 Taro • 云开发电商小程序示例 简介 hi, 大家好。我是来自京东凹凸实验室的钟鑫,Taro 框架核心开发成员。 目前我主要负责 Taro 框架多端组件及 API 相关。还有京东购物...

凹凸实验室
08/20
0
0
以太坊开发DApp实战教程——用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台

第一节 简介 欢迎和我们一起来用以太坊实战开发构建一个去中心化电商DApp!我们将会构建一个类似淘宝的在线电子商务应用,我将使用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商...

马拉喀什
2018/03/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

反编译9.png图片还原

本文链接:https://blog.csdn.net/a1140778530/article/details/10528507 经常反编译apk文件找资源,9.png的文件处理起来很麻烦。 最近使用Ant自动编译打包app时,从别处搜罗来的9.png文件导...

shzwork
6分钟前
1
0
Shell脚本应用 – for、while循环语句

一、for循环语句 在实际工作中,经常会遇到某项任务需要多次执行的情况,而每次执行时仅仅是处理的对象不一样,其他命令相同。例如:根据通讯录中的姓名列表创建系统账号等情况。 当面对各种...

linux-tao
6分钟前
1
0
RPA风潮下企业财务工作模式的变革

RPA(机器人流程自动化)在财务领域的应用,正给企业财务带来前所未有的改变。 前RPA时代,财务领域面临的痛点 在RPA机器人应用之前,企业财务工作进程的推进,主要通过财务人员人工操作或信...

UiBot
11分钟前
2
0
Hive之命令行修改表注释

最近遇到一个需求,在不重建表的情况下,修改表的注释,hive有没有类似关系型数据库的SQL命令来修改呢,找了下,亲测有效,如下List-1 List-1 hive>use your_schemahvie>ALTER TABLE tabl...

克虏伯
11分钟前
1
0
是什么,它的作用是什么

在HTML文档的首部往往会有这么一句话<!DOCTYPE html>,许多时候我们忽视了它的存在,它实际上是一个声明,告诉浏览器用哪种HTML版本的规范来解读HTML文档。 尽管我们不给出这句声明浏览器照样...

前端老手
17分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部