精华内容
下载资源
问答
  • 最近要实现的一些功能需要让ES的同义、扩展、停止能够热更新,达到让搜索更精确的目的。在网上看了很多相关的博客,现在热更新的方案已经实施成功,现在来总结一下。 ES版本:5.5.2 IK分词器版本:5.5.2 ...

    最近要实现的一些功能需要让ES的同义词、扩展词、停止词能够热更新,达到让搜索更精确的目的。在网上看了很多相关的博客,现在热更新的方案已经实施成功,现在来总结一下。

    ES版本:5.5.2

    IK分词器版本:5.5.2

    扩展词、停止词 

    我的ES使用的中文分词器是IK分词器,IK分词器支持一种热更新的方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新。

    同义词

    同义词的配置,Elasticsearch自带了一个synonym同义词插件,但是该插件只能使用文件或在分析器中静态地配置同义词,如果需要添加或修改,需要修改配置文件和重启,使用方式不够友好,我需要的是热更新。

     

    基于以上的现有的方案,再加上我参考了两篇博客,决定采用这样的方案:

    (1)修改ik分词器源码,然后手动支持从mysql中每隔一定时间,自动加载新的词库

    (2)修改一款别人自研的一个可动态维护同义词的插件,也是同样的从mysql中每隔一定时间,自动加载新的词库

    (3)在项目中对相应的文档用定时任务进行重建文档操作,因为热更新的词对旧文档无效。

    附上两位的博客地址:
    Elasticsearch之IK分词器热更新
    一个简易的Elasticsearch动态同义词插件
    万分感谢两位~

    好了,下面进入正题

    一、扩展词、停止词

    1.下载IK分词器的源码

    进入github,找到对应版本的ik分词器,下载源码,我这里是5.5.2版本的ES,所以我下载5.5.2版本的IK分词器

    https://github.com/medcl/elasticsearch-analysis-ik/tree/v5.5.2
    这是一个maven工程,下载下来后直接导入到eclipse中进行改造

    2.修改源码

    先看一下下载下来的源码的文件目录

    这个-root后缀的项目就是我刚下载下来的ik的源码,这个源码上面的那个项目是我修改过后的源码。

    由于我们是要动词库,所以我们直奔dic目录,找到Dictionary类,先看看它这个是怎么加载词典的。

    看看他的构造函数:

    initial方法:

    根据上面原博主的思路,是在这个Dictionnary类中,写两个方法,用jdbc去mysql中分别查询扩展词和停止词,然后放到对应的词典中。然后再这个初始化的方法中,像原生的代码那样也调用一下这两个方法。再建立一个监控线程,定时去加载扩展词和停止词。

    我在原博主的的基础上,把配置的数据库的属性初始化的时候封装到Dictionnary类中的一个属性中,包括加载的sql和监控线程隔多久进行一次扫描。这样方便进行配置。

    (1)、先在Dictionnary类定义两个自己要的属性

    DB_PROPERTIES属性是我们自己建的属性文件的文件名字
    myProperties属性是用来把读取到的属性文件里的属性装起来

    (2)、在config目录下创建db.properties文件

    (3)、在构造方法中读取db.properties,并提供几个获取属性的方法

    	private Dictionary(Configuration cfg) {
    		this.configuration = cfg;
    		this.props = new Properties();
    		this.myProperties = new Properties();
    		this.conf_dir = cfg.getEnvironment().configFile().resolve(AnalysisIkPlugin.PLUGIN_NAME);
    		Path configFile = conf_dir.resolve(FILE_NAME);
    		Path myFile = cfg.getConfigInPluginDir().resolve(DB_PROPERTIES);
    		logger.info("加载属性文件db.properties的路径:" + myFile);
    		InputStream input = null;
    		InputStream myInput = null;
    		File file = myFile.toFile();
    		logger.info("file文件:" + file);
    		try {
    			myInput = new FileInputStream(file);
    		} catch (FileNotFoundException e1) {
    			logger.error("db.properties未找到", e1);
    		}
    		try {
    			logger.info("try load config from {}", configFile);
    			input = new FileInputStream(configFile.toFile());
    		} catch (FileNotFoundException e) {
    			conf_dir = cfg.getConfigInPluginDir();
    			configFile = conf_dir.resolve(FILE_NAME);
    			try {
    				logger.info("try load config from {}", configFile);
    				input = new FileInputStream(configFile.toFile());
    			} catch (FileNotFoundException ex) {
    				// We should report origin exception
    				logger.error("ik-analyzer", e);
    			}
    		}
    		if (input != null) {
    			try {
    				props.loadFromXML(input);
    			} catch (InvalidPropertiesFormatException e) {
    				logger.error("ik-analyzer", e);
    			} catch (IOException e) {
    				logger.error("ik-analyzer", e);
    			}
    		}
    		try {
    			myProperties.load(myInput);
    		} catch (IOException e) {
    			logger.error("加载db.properties文件失败!", e);
    		}
    	}

    获取几个属性的方法

    	private String getUrl() {
    		String url = myProperties.getProperty("url");
    		return url;
    	}
    
    	private String getUser() {
    		String user = myProperties.getProperty("user");
    		return user;
    	}
    
    	private String getPassword() {
    		String password = myProperties.getProperty("password");
    		return password;
    	}
    
    	private int getInterval() {
    		Integer interval = Integer.valueOf(myProperties.getProperty("interval"));
    		return interval;
    	}
    
    	private String getExtWordSql() {
    		String extWordSql = myProperties.getProperty("extWordSql");
    		return extWordSql;
    	}
    
    	private String getStopWordSql() {
    		String stopWordSql = myProperties.getProperty("stopWordSql");
    		return stopWordSql;
    	}

    (3)、写两个从数据库中读取扩展词和停止词的方法,再写一个重新加载扩展词和停止词的方法给监控线程去调用

    	private void loadMySQLExtDict() {
    		Connection conn = null;
    		Statement stmt = null;
    		ResultSet rs = null;
    		try {
    			logger.info("query ext dict from mysql, " + getUrl());
    			Class.forName("com.mysql.jdbc.Driver");
    			conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
    			stmt = conn.createStatement();
    			String extWordSql = getExtWordSql();
    			if(!StringUtils.isNullOrEmpty(extWordSql)){
    				rs = stmt.executeQuery(extWordSql);
    				while (rs.next()) {
    					String theWord = rs.getString("main_keyword");
    					logger.info("main_keyword ext word from mysql: " + theWord);
    					_MainDict.fillSegment(theWord.trim().toCharArray());
    				}
    			}
    
    		} catch (Exception e) {
    			logger.error("erorr", e);
    		} finally {
    			if (rs != null) {
    				try {
    					rs.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (stmt != null) {
    				try {
    					stmt.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (conn != null) {
    				try {
    					conn.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    		}
    	}
    
    	private void loadMySQLStopDict() {
    		Connection conn = null;
    		Statement stmt = null;
    		ResultSet rs = null;
    		try {
    			logger.info("query stop dict from mysql, " + getUrl());
    			Class.forName("com.mysql.jdbc.Driver");
    			conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
    			stmt = conn.createStatement();
    			String stopWordSql = getStopWordSql();
    			if(!StringUtils.isNullOrEmpty(stopWordSql)){
    			rs = stmt.executeQuery(stopWordSql);
    				while (rs.next()) {
    					String theWord = rs.getString("main_keyword");
    					logger.info("main_keyword stop word from mysql: " + theWord);
    					_StopWords.fillSegment(theWord.trim().toCharArray());
    				}
    			}
    		} catch (Exception e) {
    			logger.error("erorr", e);
    		} finally {
    			if (rs != null) {
    				try {
    					rs.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (stmt != null) {
    				try {
    					stmt.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    			if (conn != null) {
    				try {
    					conn.close();
    				} catch (SQLException e) {
    					logger.error("error", e);
    				}
    			}
    		}
    	}
    
    	public void reLoadMySqlDict() {
    		logger.info("重新加载远程词典...");
    		// 新开一个实例加载词典,减少加载过程对当前词典使用的影响
    		Dictionary tmpDict = new Dictionary(configuration);
    		tmpDict.configuration = getSingleton().configuration;
    		tmpDict.loadMainDict();
    		tmpDict.loadStopWordDict();
    		tmpDict.loadMySQLExtDict();
    		tmpDict.loadMySQLStopDict();
    		_MainDict = tmpDict._MainDict;
    		_StopWords = tmpDict._StopWords;
    		logger.info("重新加载远程词典完毕...");
    	}

    (4)、代码到这里差不多就改完了,但是由于我的ES原来本来已经有了一个IK分词器插件,我并不想把之前的IK分词器插件替换掉,怕影响到以前的业务,所以我还需要把这个修改了源码过后的IK分词器取个别的名字。

     进入pom.xml中修改插件名字

    修改org.elasticsearch.plugin.analysis.ik.AnalysisIkPlugin类

    (5)、由于我们使用了mysql来做这个词典加载,项目中使用到了mysql连接,所以要把mysql驱动包配置到plugin.xml文件中,否则打出来的包中没有mysql的驱动包,到时候会报错。加一行配置  <include>mysql:mysql-connector-java</include>

    (6)、接下来,mvn clean package打包代码,然后找到 target\releases\elasticsearch-analysis-ik-5.5.2.zip

    (7)、解压缩该文件到es的plugin目录下,为了避免跟之前的ik文件夹重复,最好先在别的地方解压,然后给文件夹重新命名,再复制到plugin目录下。

    二、同义词

    1.下载原博主上传到github的源码

    https://github.com/ginobefun/elasticsearch-dynamic-synonym


    1.1目录结构

    1.2实现方式

    引用原博主自己写的:

    DynamicSynonymTokenFilter

    • DynamicSynonymTokenFilter参考了SynonymTokenFilter的方式,但又予以简化,使用一个HashMap来保存同义词之间的转换关系;
    • DynamicSynonymTokenFilter只支持Solr synonyms,同时也支持expand和ignore_case参数的配置;
    • DynamicSynonymTokenFilter通过数据库来管理同义词的配置,并轮询数据库(通过version字段判断是否存在规则变化)实现同义词的动态管理;

    1.3源码

    1.3.1  DynamicSynonymTokenFilterFactory

    1.3.2  SynonymRuleManager初始化方法

    loadSynonymRule()方法就是去数据库查询相应的数据,原博主提供的有一个表结构来存同义词,每一组同义词按照相应的格式就好,ES支持两种格式,一种xxxx,xxxx,xxxx,英文逗号分隔,一种xxxx,xxxx,xxxx => xxxx格式,选择其中一种就好,源码都有做判断的。

    监控线程就是重新加载数据,创建一个临时的SynonymRuleManager,加载完了数据,然后再把map赋值给原本的那个synonymMap,防止加载同义词的时候影响插件工作。跟上面IK加载扩展词什么的是一个意思。

    2.修改源码

    2.1需要改动的地方

    • 由于我们本身的业务已经有了自己的词库表,所以不能用源码提供的表以及sql,我需要按照自己的逻辑查出数据,然后按照格式放入synonymMap中。
    • 源码用到的db_url是通过ES的settings配置进行加载的,我为了和ik那样统一一下,所以我需要改变加载方式,同样的创建一个db.properties文件来存放要加载的属性,顺便把sql也提取出来,源码是在代码里把sql写死的。

    2.2修改Configuration,添加几个我自己需要的属性

    public class Configuration {
    
        private final boolean ignoreCase;
    
        private final boolean expand;
        
        private final int interval;
    
        private final String dbUrl;
        
        private final String user;
        
        private final String password;
        
        private final String sql;
    
        private final Analyzer analyzer;
        
        public final static String DB_PROPERTIES = "db.properties";
    
        public Configuration(boolean ignoreCase, boolean expand,int interval, Analyzer analyzer, String dbUrl,String user,String password,String sql) {
            this.ignoreCase = ignoreCase;
            this.expand = expand;
            this.interval = interval;
            this.analyzer = analyzer;
            this.dbUrl = dbUrl;
            this.user = user;
            this.password = password;
            this.sql = sql;
        }
    
        public Analyzer getAnalyzer() {
            return analyzer;
        }
    
        public boolean isIgnoreCase() {
            return ignoreCase;
        }
    
        public boolean isExpand() {
            return expand;
        }
    
        public String getDBUrl() {
            return dbUrl;
        }
        
        public int getInterval(){
        	return interval;
        }
        
        public String getUser(){
        	return user;
        }
        
        public String getPassword(){
        	return password;
        }
    
    	public String getSql() {
    		return sql;
    	}
    }

    2.3  在项目目录下新建config文件夹,创建db.properties文件,修改 DynamicSynonymTokenFilterFactory,加载db.properties文件

    public class DynamicSynonymTokenFilterFactory extends AbstractTokenFilterFactory {
    	public DynamicSynonymTokenFilterFactory(IndexSettings indexSettings, Environment env, String name,
    			Settings settings) throws IOException {
    		super(indexSettings, name, settings);
    
    		// get the filter setting params
    		final boolean ignoreCase = settings.getAsBoolean("ignore_case", false);
    		final boolean expand = settings.getAsBoolean("expand", true);
    		File file = PathUtils
    				.get(new File(DynamicSynonymPlugin.class.getProtectionDomain().getCodeSource().getLocation().getPath())
    						.getParent(), "config")
    				.toAbsolutePath().resolve(Configuration.DB_PROPERTIES).toFile();
    		final Properties dbProperties = new Properties();
    		logger.info("加载DynamicSynonym sql properties file" + file);
    		try {
    			dbProperties.load(new FileReader(file));
    		} catch (IOException e) {
    			logger.info("加载数据库属性文件" + Configuration.DB_PROPERTIES + "失败!");
    			e.printStackTrace();
    		}
    		final String tokenizerName = settings.get("tokenizer", "whitespace");
    
    		Analyzer analyzer;
    		if ("standand".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new StandardAnalyzer();
    		} else if ("keyword".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new KeywordAnalyzer();
    		} else if ("simple".equalsIgnoreCase(tokenizerName)) {
    			analyzer = new SimpleAnalyzer();
    		} else {
    			analyzer = new WhitespaceAnalyzer();
    		}
    
    		// NOTE: the manager will only init once
    		SynonymRuleManager
    				.initial(new Configuration(ignoreCase, expand, Integer.valueOf(dbProperties.getProperty("interval")),
    						analyzer, dbProperties.getProperty("url"), dbProperties.getProperty("user"),
    						dbProperties.getProperty("password"), dbProperties.getProperty("sql")));
    	}
    
    	@Override
    	public TokenStream create(TokenStream tokenStream) {
    		return new DynamicSynonymTokenFilter(tokenStream);
    	}
    }
    

    2.4 JDBCUtils新建一个我自己的查询数据库的方法querySynonymAiLikenessKeywords()

        public static List<String> querySynonymAiLikenessKeywords(String dbUrl,String user,String password,String sql) throws Exception {
            List<String> list = new ArrayList<String>();
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection(dbUrl,user,password);
                stmt = conn.createStatement();
    //            String sql = "SELECT main_keyword,likeness_keywords FROM ai_keyword WHERE keyword_type = 1 AND is_delete = 1";
                if(!StringUtils.isNullOrEmpty(sql)){
                	rs = stmt.executeQuery(sql);
                	while (rs.next()) {
                		String likenessKeywords = rs.getString("likeness_keywords");
                		if(!StringUtils.isNullOrEmpty(likenessKeywords)){
                			String keywordAndlikenessKeywords = likenessKeywords + rs.getString("main_keyword");
                			list.add(keywordAndlikenessKeywords);
                		}
                	}
                }
            } finally {
                closeQuietly(conn, stmt, rs);
            }
            return list;
        }

    2.5 修改SynonymRuleManager.loadSynonymRule()方法和reloadSynonymRule()方法

        private void loadSynonymRule() {
        	// 回头修改一下,不需要返回值,只需要把数据查询出来就可以了
            try {
                List<String> synonymRuleList = JDBCUtils.querySynonymAiLikenessKeywords(configuration.getDBUrl(),configuration.getUser(),configuration.getPassword(),configuration.getSql());
                this.synonymMap = new SimpleSynonymMap(this.configuration);
                for (String rule : synonymRuleList) {
                    this.synonymMap.addRule(rule);
                }
    
                LOGGER.info("Load {} synonym rule succeed!", synonymRuleList.size());
            } catch (Exception e) {
                LOGGER.error("Load synonym rule failed!", e);
                //throw new RuntimeException(e);
            }
        }
    
        public void reloadSynonymRule() {
        	// 也需要修改,把数据放到单例的manager中
            LOGGER.info("Start to reload synonym rule...");
            try {
                SynonymRuleManager tmpManager = new SynonymRuleManager();
                tmpManager.configuration = getSingleton().configuration;
                List<String> synonymRuleList = JDBCUtils.querySynonymAiLikenessKeywords(configuration.getDBUrl(),configuration.getUser(),configuration.getPassword(),configuration.getSql());
                SimpleSynonymMap tempSynonymMap = new SimpleSynonymMap(tmpManager.configuration);
                for (String rule : synonymRuleList) {
                    tempSynonymMap.addRule(rule);
                }
    
                this.synonymMap = tempSynonymMap;
                LOGGER.info("Succeed to reload {} synonym rule!", synonymRuleList.size());
            } catch (Throwable t) {
                LOGGER.error("Failed to reload synonym rule!", t);
            }
    
        }

     2.6 修改Monitor.run()方法,直接调用SynonymRuleManager.reloadSynonymRule()就好了

    public class Monitor implements Runnable {
    
        @Override
        public void run() {
        	// 线程任务,直接调用reloadSynonymRule
        	SynonymRuleManager.getSingleton().reloadSynonymRule();
        }
    
    }

    这样就基本上可以了,接下来打包mvn clean package,打包之前别忘了,这个插件也用了mysql,跟上文一样需要到plugin.xml中去修改配置,这里我就不贴图了。打包完成后找到target/releases/下的.zip包,在ES的plugin包下新建一个文件夹,把zip包解压到该目录。到此为止两个插件修改完毕,看看plugin目录:

     

     

    三、配置

    由于同义词过滤器需要配置,Elasticsearch自带的同义词过滤器在分析器配置的话配置如下:

    {
        "index" : {
            "analysis" : {
                "analyzer" : {
                    "synonym_analyzer" : {
                        "tokenizer" : "whitespace",
                        "filter" : ["my_synonym"]
                    }
                },
                "filter" : {
                    "my_synonym" : {
                        "type" : "synonym",
                        "expand": true,
                        "ignore_case": true, 
                        "synonyms_path" : "analysis/synonym.txt"
                        "synonyms" : ["阿迪, 阿迪达斯, adidasi => Adidas","Nike, 耐克, naike"]
                    }
                }
            }
        }
    }

    所以,想要使用上两个插件就需要对文档进行设置,设置它的analyzer和filter。

    由于使用的ES版本问题,5.5.2版本已经不支持在elasticsearch.yml中进行,所以只能通过restfulAPI进行配置

    在ES中新建一个文档,通过URL进行设置

    PUT ES访问地址:9200/新建的文档/
    {
        "settings": {
            "analysis": {
                "analyzer": {
                    // 自定义一个analyzer
                    "ik_max_word_synonym": {
                        // filter用我们自定义的filter
                        "filter": [
                            "my_synonym"
                        ],
                        "tokenizer": "ik_max_word_custom",// 我们修改后的ik分词器的名字
                        "type": "custom"
                      }
                    },
                    "filter": {
                        // 自定义一个filter
                        "my_synonym": {
                        "expand": true,
                        "ignore_case": true,
                        "tokenizer": "ik_max_word_custom",// 这个filter的tokenizer也用修改后的ik
                         "type": "dynamic-synonym"// 类型是我们修改后的插件
                   }
                }
            }
        }
    }

    设置成功后

    这时候就可以使用了。

    接下来还有几个问题:

    (1)如果每新建一个文档,要用到这样配置的时候,都这么手动配置太麻烦了,而且几套环境,每一个都去配置,这样不太友好。

    (2)重建文档问题,新加入词库的词语对于旧的文档不生效,所以需要有一个定时任务去定时重建文档。

    所以接下来我就在项目中把这些实现了一下

    项目是一个springboot项目,最开始使用ES的时候,为了图方便,使用的是elasticsearch spring data jpa ,用repository来操作的ES。现在要对settings进行设置,所以我想使用elasticsearchTemplate来进行设置,但是当用elasticsearchTemplate的时候就导致实体类上的注解@Field中配置的映射关系什么的都不生效了。之后网上找了一下才发现,当使用TransportClient客户端的时候会导致那个映射关系失效的。那就索性就不让jpa帮我生成文档了,生成文档的这一步由我自己来操作。

    就直接设置@Document注解中的属性createIndex的值为false。

    顺便提一下,由于几套环境用的是同一个ES,所以我做了一下动态indexName的操作,通过配置文件来加载索引名字。

    首先在配置文件application.properties中设置一个属性

    然后建立一个配置类

    EsIndexNameConfig
     

    @Component("esIndexNameConfig")
    public class EsIndexNameConfig {
    
    	@Value("${ai_robot}")
    	private String aiRobotindexName;
    
    	public String getAiRobotindexName() {
    		return aiRobotindexName;
    	}
    
    	public void setAiRobotindexName(String aiRobotindexName) {
    		this.aiRobotindexName = aiRobotindexName;
    	}
    
    }

    设置一个成员变量,让这个成员变量加载application.properties中的属性

    之后在@Document中用 #{esIndexNameConfig.aiRobotindexName}就能取到indexName了。

    好的,接下来继续手动生成文档settings和mapping

    1.建立接口类EsSettingsMap

    由于考虑到以后可能还有其他文档需要自定义settings的Map,所以弄一个通用的接口类,以后再有新增的文档,就只需要实现这个接口就可以加载了。

    /**
     * 如果需要把一个映射到es的实体类实现动态加载setting和mapping需要实现此接口,需要把实体类的@Document注解中的属性createIndex = false
     * EsIndexSettingAndMappingInit类会加载实现类getSettingsMap()方法返回的settingsMap和实体类上@Field注解配置的mapping
     * @author zhangch 2019-05-05
     */
    public interface EsSettingsMap {
    
    	/**
    	 * 获取一段settings配置,例如:
    	 * {
    			    "settings": {
    			        "analysis": {
    			            "analyzer": {
    			                "ik_max_word_synonym": {
    			                    "filter": [
    			                        "my_synonym"
    			                    ],
    			                    "tokenizer": "ik_max_word",
    			                    "type": "custom"
    			                }
    			            },
    			            "filter": {
    			                "my_synonym": {
    			                    "expand": true,
    			                    "ignore_case": true,
    			                    "tokenizer": "ik_max_word",
    			                    "type": "dynamic-synonym"
    			                }
    			            }
    			        }
    			    }
    			}
    		这段配置需要的map:
    		
    		Map<String, Object> ik_max_word_synonym  = new HashMap<>(3);
    		ik_max_word_synonym.put("filter", new String[]{"my_synonym"});
    		ik_max_word_synonym.put("tokenizer", "ik_max_word_custorm");
    		ik_max_word_synonym.put("type", "custom");
    		Map<String, Object> analyzer = new HashMap<>(1);
    		analyzer.put("ik_max_word_synonym", ik_max_word_synonym);
    		Map<String, Object> my_synonym  = new HashMap<>(4);
    		my_synonym.put("expand", true);
    		my_synonym.put("ignore_case", true);
    		my_synonym.put("tokenizer", "ik_max_word_custorm");
    		my_synonym.put("type", "dynamic-synonym");
    		Map<String, Object> filter = new HashMap<>(1);
    		filter.put("my_synonym", my_synonym);
    		Map<String, Object> analysis = new HashMap<>(2);
    		analysis.put("analyzer", analyzer);
    		analysis.put("filter", filter);
    		Map<String, Object> settings = new HashMap<>(1);
    		settings.put("analysis", analysis);
    	 * @return settings;
    	 */
    	Map<String, Object> getSettingsMap();
    	
    	
    	Class<?> getOperatorClass();
    	
    }
    

    2.实现接口

    /**
     * @ClassName:  AiRobotEsSettingsMap   
     * @Description: 实体类AiRobotPo映射的es文档自定义的settings
     * @author: zhangch
     * @date:   2019年5月5日 下午5:55:37
     */
    public class AiRobotEsSettingsMap implements EsSettingsMap {
    
    	@Override
    	public Map<String, Object> getSettingsMap() {
    		/**
    		 * {
    			    "settings": {
    			        "analysis": {
    			            "analyzer": {
    			                "ik_max_word_synonym": {
    			                    "filter": [
    			                        "my_synonym"
    			                    ],
    			                    "tokenizer": "ik_max_word_custorm",
    			                    "type": "custom"
    			                }
    			            },
    			            "filter": {
    			                "my_synonym": {
    			                    "expand": true,
    			                    "ignore_case": true,
    			                    "tokenizer": "ik_max_word_custorm",
    			                    "type": "dynamic-synonym"
    			                }
    			            }
    			        }
    			    }
    			}
    		 */
    		Map<String, Object> ik_max_word_synonym  = new HashMap<>(3);
    		ik_max_word_synonym.put("filter", new String[]{"my_synonym"});
    		ik_max_word_synonym.put("tokenizer", "ik_max_word_custorm");
    		ik_max_word_synonym.put("type", "custom");
    		Map<String, Object> analyzer = new HashMap<>(1);
    		analyzer.put("ik_max_word_synonym", ik_max_word_synonym);
    		Map<String, Object> my_synonym  = new HashMap<>(4);
    		my_synonym.put("expand", true);
    		my_synonym.put("ignore_case", true);
    		my_synonym.put("tokenizer", "ik_max_word_custorm");
    		my_synonym.put("type", "dynamic-synonym");
    		Map<String, Object> filter = new HashMap<>(1);
    		filter.put("my_synonym", my_synonym);
    		Map<String, Object> analysis = new HashMap<>(2);
    		analysis.put("analyzer", analyzer);
    		analysis.put("filter", filter);
    		Map<String, Object> settings = new HashMap<>(1);
    		settings.put("analysis", analysis);
    		return settings;
    	}
    
    	@Override
    	public Class<?> getOperatorClass() {
    		return QuestionPo.class;
    	}
    
    }
    

    3.建立init类,实现ApplicationRunner接口,让程序在启动的时候就初始化这个设置。逻辑是先获取到EsSettingsMap下的所有实现类,我这里实现的比较简单,默认只找跟这个接口在一个包下所有实现类,所以回头建立实现类的时候只能跟这个接口放在同一目录下。获取到所有实现类后,遍历实现类,先判断这个index在ES是否存在,如果存在就不用管了(之前已经生成过了)。如果不存在,就创建索引,把settingsMap传进去。并且putMapping()一下,这个操作是使实体类上的注解@field中配置的mapping信息生效。

    /**
     * 初始化es的index的setting和mapping,配置自定义filter和analyzer,并且使po上的mapping配置信息生效
     * 
     * @author zhangch 2019-04-29
     *
     */
    @Component
    public class EsIndexSettingAndMappingInit implements ApplicationRunner {
    
    	private Logger logger = LoggerFactory.getLogger(EsIndexSettingAndMappingInit.class);
    
    	@Autowired
    	private ElasticsearchTemplate elasticsearchTemplate;
    
    	@Override
    	public void run(ApplicationArguments args) throws Exception {
    		// 获取EsSettingsMap的所有实现类,加载他们的settings和mapping
    		logger.info("---------------------------初始化es的setting和mapping--------------------------------");
    		try {
    			List<Class<?>> allAssignedClass = ClassUtil.getAllAssignedClass(EsSettingsMap.class);
    			logger.info("需要加载{}个实现类", allAssignedClass.size());
    			for (Class<?> c : allAssignedClass) {
    				try {
    					EsSettingsMap map = (EsSettingsMap) c.newInstance();
    					Map<String, Object> settingsMap = map.getSettingsMap();
    					boolean indexExists = elasticsearchTemplate.indexExists(map.getOperatorClass());
    					logger.info("索引{}-{}存在",
    							elasticsearchTemplate.getPersistentEntityFor(map.getOperatorClass()).getIndexName(),
    							indexExists ? "" : "不");
    					if (!indexExists) {
    						logger.info("加载settingMap:{},加载settings:{}", c.getName(), settingsMap);
    						elasticsearchTemplate.createIndex(map.getOperatorClass(), settingsMap);
    						elasticsearchTemplate.putMapping(map.getOperatorClass());
    					}
    				} catch (InstantiationException | IllegalAccessException e) {
    					logger.info("加载settings失败!");
    					e.printStackTrace();
    				}
    			}
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    ClassUtils的getAllAssignedClass()方法

    	/**
    	 * 获取同一路径下所有子类或接口实现类
    	 * 
    	 * @param intf
    	 * @return
    	 * @throws IOException
    	 * @throws ClassNotFoundException
    	 */
    	public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws IOException, ClassNotFoundException {
    		List<Class<?>> classes = new ArrayList<Class<?>>();
    		for (Class<?> c : getClasses(cls)) {
    			if (cls.isAssignableFrom(c) && !cls.equals(c)) {
    				classes.add(c);
    			}
    		}
    		return classes;
    	}

    这样就实现了在项目中自己配置settings了。

    接下来是 定时任务重建文档

    springboot的定时任务比较简单,直接用注解就可以了

    在启动类上,加上@EnableAsync和@EnableScheduling注解

    第二个注解是开启定时任务开关,第一个注解是开启多线程定时任务,避免相同时间的定时任务有冲突。

    建立定时任务类EsScheduled
     

    @Component
    public class EsScheduled {
    
    	@Autowired
    	private ElasticsearchTemplate elasticsearchTemplate;
    
    	private Logger logger = LoggerFactory.getLogger(EsScheduled.class);
    
    	/**
    	 * 重建项目中动态索引的po在es中的文档
    	 */
    	@Scheduled(cron = "0 5 0 * * *")
    	public void reBuildDocument() {
    		List<Class<?>> allAssignedClass = null;
    		try {
    			allAssignedClass = ClassUtil.getAllAssignedClass(EsSettingsMap.class);
    		} catch (ClassNotFoundException | IOException e) {
    			e.printStackTrace();
    		}
    		if (allAssignedClass != null && allAssignedClass.size() > 0) {
    			for (Class<?> clazz : allAssignedClass) {
    				EsSettingsMap map;
    				try {
    					map = (EsSettingsMap) clazz.newInstance();
    					boolean indexExists = elasticsearchTemplate.indexExists(map.getOperatorClass());
    					String indexName = elasticsearchTemplate.getPersistentEntityFor(map.getOperatorClass())
    							.getIndexName();
    					logger.info("索引 {} - {} 存在", indexName, indexExists ? "" : "不");
    					if (indexExists) {
    						UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE
    								.newRequestBuilder(elasticsearchTemplate.getClient());
    						updateByQuery.source(indexName).abortOnVersionConflict(false);
    						BulkByScrollResponse response = updateByQuery.get();
    						long updated = response.getUpdated();
    						logger.info("更新 {} 中的文档 {} 条", indexName, updated);
    					}
    				} catch (InstantiationException | IllegalAccessException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    

    逻辑也不复杂,也是遍历实现类,看看索引是否存在,如果存在就更新index中的所有记录。我设置的是每天零点过5分扫描。

     

    好了,这样的话整体的方案已经实现了,通过修改ik分词器的源码和以为博主自己写的插件的源码,然后跟项目搭配,实现同义词、扩展词、停止词的热更新并尽量保证词库更新后的效果。

     

    看看测试效果吧

    我数据库中的表结构是这样的

    CREATE TABLE `ai_keyword` (
      `keyword_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关键词id',
      `mid` int(11) DEFAULT NULL COMMENT '企业id',
      `eid` int(11) DEFAULT NULL COMMENT '业务员id',
      `keyword_type` int(2) NOT NULL COMMENT '关键词类型  1--标准词  2--专有名词  3--停止词',
      `main_keyword` varchar(20) NOT NULL COMMENT '标准词',
      `likeness_keywords` varchar(255) DEFAULT NULL COMMENT '相似词数组(专有名词和停止词无此项)',
      `is_delete` int(2) DEFAULT '1' COMMENT '是否删除  0--已删除  1--未删除',
      `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
      `delete_time` timestamp NULL DEFAULT NULL COMMENT '删除时间',
      `standby1` varchar(255) DEFAULT NULL COMMENT '备用字段1',
      `standby2` varchar(255) DEFAULT NULL COMMENT '备用字段2',
      PRIMARY KEY (`keyword_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COMMENT='关键词表';

    添加几条数据

    测试

    返回数据

    {
        "tokens": [
            {
                "token": "lol",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 0
            },
            {
                "token": "英雄联盟",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 1
            },
            {
                "token": "毒害小学生",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 2
            },
            {
                "token": "王者荣耀",
                "start_offset": 0,
                "end_offset": 4,
                "type": "SYNONYM",
                "position": 3
            },
            {
                "token": "王者",
                "start_offset": 0,
                "end_offset": 2,
                "type": "CN_WORD",
                "position": 4
            },
            {
                "token": "荣耀",
                "start_offset": 2,
                "end_offset": 4,
                "type": "CN_WORD",
                "position": 5
            }
        ]
    }

     可以看到,扩展词和同义词都已经生效了。

    再添加一条停止词试试

    先分词“你要这个干什么”

    结果

    {
        "tokens": [
            {
                "token": "你",
                "start_offset": 0,
                "end_offset": 1,
                "type": "CN_CHAR",
                "position": 0
            },
            {
                "token": "要",
                "start_offset": 1,
                "end_offset": 2,
                "type": "CN_CHAR",
                "position": 1
            },
            {
                "token": "这个",
                "start_offset": 2,
                "end_offset": 4,
                "type": "CN_WORD",
                "position": 2
            },
            {
                "token": "干什么",
                "start_offset": 4,
                "end_offset": 7,
                "type": "CN_WORD",
                "position": 3
            },
            {
                "token": "干什",
                "start_offset": 4,
                "end_offset": 6,
                "type": "CN_WORD",
                "position": 4
            },
            {
                "token": "什么",
                "start_offset": 5,
                "end_offset": 7,
                "type": "CN_WORD",
                "position": 5
            }
        ]
    }

    添加停止词“什么”

    测试效果

    结果中没有把“什么”这个词给分出来,证明已经被添加到停止词词库了。

     

    好了,这就是我这些天研究的一些乱七八糟的东西的总结,希望能对看到的你有所帮助~~~

     

     

    展开全文
  • 对应地形容flexible. e.g. My schedule is flexible next week. 我下周地日程安排比较灵活。 (4) more or less 或多或少 e.g. The project was more or less a success. 这个工程大体上时成功的。 (5) autonomous...

    1. 今日话题

    In your opinion, why is it hard of for university students to manage their time effectively?

    2. 示范答案

    I think time management is particularly hard of university students because: First of all, many of them lack time management skills. For most students ,daily life before uni is pretty much all planned out of them. There's not much space for them to learn how to manage time on their own.

    Also there's more flexibility with the schedule in uni. Unlike in high school, time management in uni is more or less autonomous. Students are on their own when it comes to choosing the right classes and planning their social activities. This can be a little overwhelming for them at the beginning.

    In my opinion, university is a period when students learn how to manage their time. I'm sure many students feel that they can manage their time more effectively after uni.

    3. 核心词句

    (1) lack  v.缺乏;没有。作名词时通常和of连用,lack of...

    e.g. The killer got away  because there is lack of evidence.

    因为证据不足,凶手逃脱了。

    (2) on one's own = by oneself 独自地

    e.g.  on my own ,on your own , on our own , on their own .

    (3) flexibility  n.灵活性。对应地形容词flexible.

    e.g. My schedule is flexible next week.

    我下周地日程安排比较灵活。

    (4) more or less 或多或少

    e.g. The project was more or less a success.

    这个工程大体上时成功的。

    (5) autonomous   adj. 自主的;自治的。注意重音在第二个音节上。

    e.g. Inner Mongolia is an autonomous region.

    内蒙古是一个自治区。

    (6) when it comes to ... 当提到。。。; 就。。。而论

    e.g. When it comes to computers. Tim is the expert.

    提到电脑,Tim就是专家了。

    (7) overwhelming   adj.巨大的

    大学的说法

    综合性的大学是university, 在英式英语口语中通常缩读为uni, 上大学通常说go to uni, 但在美式英语中,上大学是go to college。

    展开全文
  • 课 题 电子英汉词典 专业班级 纺工工程 学生姓名 学 号 指导老师 田媛 审 批 任务书下达日期 2013 年 12 月 26 日 任务完成日期 2014年 01 月 06 日 一、设计内容与设计要求 1.设计内容: 课题一:电子英汉词典。...

    PAGE

    课 程 设 计 报 告

    课程名称 C语言课程设计

    课题名称 电子英汉词典

    专 业 纺织服装学院

    班 级 纺工1203

    学 号

    姓 名

    指导教师 田 媛

    2014年 01 月06 日

    湖南工程学院

    课 程 设 计 任 务 书

    课程名称 C语言课程设计

    课 题 电子英汉词典

    专业班级 纺工工程

    学生姓名

    学 号

    指导老师 田媛

    审 批

    任务书下达日期 2013 年 12 月 26 日

    任务完成日期 2014年 01 月 06 日

    一、设计内容与设计要求

    1.设计内容:

    课题一:电子英汉词典。具体内容见附录。

    2.设计要求:

    1)设计正确,方案合理。

    2)界面友好,使用方便。

    3)程序精炼,结构清晰。

    4)上机演示。

    3.设计报告要求:

    课程设计报告格式如下:

    1)正文的格式:一级标题用3号黑体,二级标题用四号宋体加粗,正文用小四号宋体,行距为22。

    2)正文的内容:课题的主要功能、课题的功能模块的划分、主要功能的实现、程序调试、总结、附件(所有程序的源代码,要求对程序写出必要的注释),课程设计报告需5000字左右(不含附件)。

    3)课程设计报告装订顺序:封面、任务书、目录、正文、评分、程序清单附件。

    二、进度安排

    星期日

    (17周)

    星期一

    (18周)

    星期四

    (18周)

    星期五

    (18周)

    星期日

    (18周)

    14:00-17:00

    18:00-21:00

    18:00-21:00

    18:00-21:00

    交报告

    上课时间另外安排上课时间另外安排。

    附录:

    设计课题三:电子英汉词典

    一、问题描述:

    该设计实现简单电子英汉词典的功能,具体管理操作包括单词的添加、显示、查找、删除、修改和保存等。

    二、功能描述:

    1、本设计采用结构体数组,每个数据的结构应当包括:单词的英文拼写,单词的中文释义。

    2、系统功能:

    词条录入:即添加单词记录。

    信息显示:将所有的单词按字母顺序显示。

    词条修改:对已经输入的单词信息进行修改。

    词条删除:删除某个单词记录。

    单词查询: 输入单词英文拼写,输出该单词的中文释义。

    信息保存:将单词信息保存到文件。

    退出系统

    3、系统使用说明:执行一个具体的功能之后,程序将重新显示功能菜单。系统的功能并不限于上述,可以对其进行扩充完善,如在对信息进行修改和删除时,可以考虑系统的安全性,在执行前若输入正确密码,才可进行操作。

    三、测试数据:

    要求被选用的词条有30个左右,简单单词为主。目录

    1.封面1

    2.任务书2

    2.1设计内容与设计要求3

    2.2附录4

    3.目录5

    4.课题的主要功能

    4.1 各函数的主要功能6

    4.2 课题功能模块的划分7

    4.3 主要功能的实现7

    4.3.1各被调用功能函数实现8

    5.程序调试9

    6.总结与体会10

    7.附件(源代码及程序运行截图)11

    8.评分表20

    (一)、课题的主要功能

    电子英汉词典设计系统能完成一个简单的电子英汉词典的功能,这个系统有七个功能,如:查找、增加、删除、修改、浏览、维护、结束 。把这七功能编该成七个子函数。在主函数当中对这七个子数调用,来实现对整个系统的操作。本词典采用一个包含50个数据结构数,每个数据的结构当包括,英文单词、中文意思。

    、各函数的主要功能

    1)程序的模块组成:

    主 函 数: main()

    初始化函数: init()

    输入函数 : scanf()

    输出函数: printf()

    删除函数: memset()

    查找函数 find()

    增加函数 add()

    删除函数 del()

    修改函数 modify()

    浏览函数 view()

    维护函数 store()

    结束函数 return()

    2) 各个函数的主要功能:

    展开全文
  • 基于SSM框架实现一个完整的学生管理系统

    千次阅读 多人点赞 2020-10-09 13:12:22
    这几天用SSM + Redis实现了一个较为完整的学生管理系统,感兴趣的同学可以下载来看看,项目注释写得很多,不失为一个学习的好项目。 Github地址:https://github.com/blizzawang/stu_system 由于项目中使用了Redis...

    这几天用SSM + Redis实现了一个较为完整的学生管理系统,感兴趣的同学可以下载来看看,项目注释写得很多,不失为一个学习的好项目。

    Github地址:https://github.com/blizzawang/stu_system

    由于项目中使用了Redis作为MyBatis的二级缓存,所以你还需要搭建一下Redis的环境,并修改RedisCache类中的主机ip:

    在这里插入图片描述

    若是你不想使用Redis,或者你还没有学习过Redis,你可以选择在项目中移除掉Redis,将Mapper配置文件中关于Redis的配置项去掉即可:

    <cache flushInterval="60000" size="1024" readOnly="true" eviction="FIFO" type="com.wwj.util.RedisCache"/>
    

    需要注意的是,因为项目中使用的Spring版本为4.3.7,所以你的jdk版本至少得是1.7,建议使用jdk1.8和tomcat8.0。

    本项目包含五个页面,分别为:添加学生信息、学生信息列表、登录、中注册、更新学生信息。

    效果图如下:
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    登录模块

    登录模块含有登录注册功能,通过点击页面上的注册按钮可以跳转至注册页面,登录注册页面均进行了部分校验,比如注册时用户名重复、登录时用户名不存在等。

    在这里插入图片描述

    在这里插入图片描述

    登录页面还实现了锁定功能,当输入密码错误超过三次后,系统将锁定该用户,被锁定后,即使密码输入正确也无法再登录系统,需要等待五分钟后才能重新登录。

    在这里插入图片描述

    锁定时间可以在MyTimer类中进行修改:

    在这里插入图片描述

    这是一个定时器,每隔一分钟会执行一次,所以当你将count设置为一个value值后,系统就会在几分钟后重新激活用户,原理是改变用户状态,0为锁定,1为激活。

    学生列表模块

    该页面会显示出数据表中的所有学生信息,并提供分页功能,左上角有欢迎词,右上角显示当前时间但并不是实时显示的,安全退出按钮可以退出当前系统回到登录页面。

    在这里插入图片描述

    分页功能有一个小细节,当你处在首页时,上一页按钮将会隐藏;同理,当你处在末页时,下一页按钮将会隐藏。

    由于删除功能比较简单,这里直接使用Ajax在原页面实现删除功能,当你点击某个条目的删除按钮时,会提示是否确认删除该学生信息:

    在这里插入图片描述

    当点击确定后,页面会重新加载,对应的学生信息会被删除。

    更新模块

    点击条目上的更新按钮,会跳转至更新页面:

    在这里插入图片描述

    更新页面会回显对应的学生信息,并做了部分校验,比如当你未做修改时,系统会进行提示:

    在这里插入图片描述

    当你填入空内容时,系统也会提示输入为空:

    在这里插入图片描述

    页面左上角的 回到主页 按钮能够回到学生列表页面。

    添加模块

    点击学生列表页面左下角的 添加学生信息 按钮可以跳转至添加页面:

    在这里插入图片描述

    你需要填入学生信息,并点击确认提交,系统会自动跳转至学生列表页面,同样地,点击左上角的 回到主页 按钮能够回到学生列表页面。

    以上便是该系统的所有功能,类中的注释我都写得非常详细,若是你正在从SSM框架过渡到SpringBoot微服务,或者是急需一个项目来练习一下SSM框架之间的整合,那么本项目无疑非常适合你。

    开源不易,若本项目帮助到了你,可以给项目点个star

    展开全文
  • [toc]学生管理系统数据库设计设计一套数据库首先要熟悉当前系统有哪些功能,具体的业务流程是什么学生管理系统功能介绍一套学校用的学生管理系统,最核心的功能如下学生信息管理(增加学生,删除学生,修改学生,查询学生...
  • 完整建库建表SQL命令参考学生管理系统数据库设计设计一套数据库首先要熟悉当前系统有哪些功能,具体的业务流程是什么学生管理系统功能介绍一套学校用的学生管理系统,最核心的功能如下学生信息管理(增加学生,删除学生,...
  • 嵌套词典,使得调取数据以及增添和删除学生信息、修改学生信息非常方便,实用性较强。 2.3 重复性 为了能显示所有学生的详细信息,有一个重复打印的过冲,需要利用for循环。加一些限定条件来消除重复但是这样做不但...
  • 其它情感词典 其它情感词典包括张伟、刘缙等编著的《学生褒贬义词典》、史继林、朱英贵 编著《褒义词典》以及杨玲、朱英贵编著的《贬义词典》。对于微博评论等网络风格比较浓烈的文本情感分析来说,还有一些...
  • 我的疑问是 "使用外键约束" ,然后我对 "外键" 这个不是很理解,查询相关资料都是讲一些术语,说外键的主要作用是:保持数据的一致性、完整性。听得我是一头雾水。关于外键,我有自己的一些理解,但是不晓得是否...
  • 考虑到的词素或义原和的描述/定义中的存在一种局部语义对应关系—如图3中的例子中“expressway”的“express-”与“quickly”、“-way”与“road”分别对应,且义原也有类似的对应关系——因此对于这两个特征...
  • MySQL 数据库 -- 学生管理系统数据库设计目录学生管理系统数据库设计学生管理系统功能介绍数据库设计步骤第 1 步: 找对象第 2 步: 找属性第 3 步: 找关系建表遵守原则第 4 步: 找特例: 等级明显而且不包含敏感数据的...
  • 英汉电子词典C语言课程设计 C 语言程序设计 课程设计(论文)题目: 英汉电子词典 院(系): 专业班级: 学 号: 学生姓名: 指导教师: 教师职称: 起止时间: 课程设计(报告)任务及评语院(系):软件学院 教研室:软件...
  • 原创:中科院物理所作者:Elias Riedel Gårding翻译:loulou审校:Nothing世界是由什么组成的?即使问这个问题都显得有点天真,更不用说去期待一个多么成熟明智的答案了。但是科学的一大奇迹就是从简单的问题中获得...
  • 但是其中文分词效果不是很理想,如“贵州财经大学”总是切分成“贵州”、“财经”和“大学”,这是因为词典中这些的权重较高。这篇文章主要介绍最经典的自然语言处理工具之一——Jieba,包括中文分词、添加自定义...
  • —THE END— 文章推荐 ☞各种编程语言的优缺点 ☞史上最狠的论文评审意见(没投过SCI,你简直不知道reject这个有多可怕呀) ☞理解矩阵背后的现实意义 ☞想追她?先算算你要等多久 ☞如何让AI读懂大佬的微表情? ...
  • 一、学生信息管理系统 本章使用Python语言开发了一个学生信息管理系统,该系统可以帮助教师快速录入学生的信息,并且对学生的信息进行基本的增、删、改、查操作;还可以根据排序功能,宏观地看到学生成绩从高到低...
  • 写在前面市面上英英词典的种类和数量可谓非常多,但这些不同品牌和类型的英英词典之间具体有什么区别?对学习者来说,究竟哪一本英英词典才适合自己?带着这些疑问,我们挑选了市面上相对主流的针对语言学习者开发的...
  • 不定代词是不指明代替任何特定名词或形容的代词;在句中可以作主语、宾语、表语、定语和状语。常用不定代词有:some(something,somebody,someone),any(anything,anybody,anyone), no(nothing,nobody,...
  • 个人疑问是 "使用外键约束" ,而后我对 "外键" 这个不是很理解,查询相关资料都是讲一些术语,说外键的主要做用是:保持数据的一致性、完整性。听得我是一头雾水。关于外键,我有本身的一些理解,可是不晓得是否...
  • 欧标是什么?怎么分的等级呢?

    千次阅读 2021-05-22 10:32:03
    首先,打算考法语考试的同学一定要了解一个标准:CECRL, 也就是我们常说的欧标。全称是 cadreeuropéen commun de referencepour les langues, 即欧洲语言共同参考框架。...具体每个阶段的要求是什么...
  • 学生会纳新自我介绍

    2021-07-04 00:21:24
    下面是小编为大家整理的学生会纳新自我介绍稿,仅供参考。学生会纳新自我介绍稿篇一:大家好!我是高一九班的朱晨迪。我很想很想加入二中的学生会。因为作为学生会干部,可以锻炼自己的能力,并可以让二中更加卓越。...
  • 是否值得掌握一门编程? 这是个数据大爆炸的时代,开源的骑士精神总是不断被提及;...故此,我推荐医学生能掌握一门自己熟悉的编程语言。 临床医学生科研上常用的知识就是医学统计学,一般的人选择用spss。但是s
  • (3.0分)【填空题】环球信息网的缩写是什么()【单选题】下列错误信息中,()是异常对象的名字。 Traceback (most recent call last): File "D:/PythonCode/Chapter08/异常.py" ,line 1, in 1/0 (3.0分)【...
  • 1. 名词 (1个一类,7个二类,5个三类) n 名词 nr 人名 nr1 汉语姓氏 nr2 汉语名字 nrj 日语人名 nrf 音译人名 ns 地名 nsf 音译地名 nt 机构团体名 nz 其它专名 ...2. 时间(1个一类,1个二类) ...4. 方位(...
  • 英汉电子词典设计报告_设计_C语言_C语言程序设计课程设计课程名称 :C语言程序课程设计题目名称 :电子英汉词典学生学院 :电气信息学院专业班级 :自动化1101学 号 :201101020104学生姓名 :胡拚联系方式 指导教师...
  • C语言课程设计 课 题 电子英汉词典 专业班级 自动化1203 学生姓名 李煜明 学 号 201201020304 指导老师 欧阳湘江 田媛 张晓清 审 批 任务书下达日期 2012 年 12 月 27日 任务完成日期 2013年 1 月 4日 一、设计内容...
  • 2)详细设计:包括主要功能模块的算法设计思路以及对应的工作流程图; 3)主要源程序代码:包括存储结构设计说明,以及完整源程序清单; 4)调试分析过程描述:包括测试数据、测试输出结果,以及对程序调试过程中存在...
  • 本篇文章将分享gensim向量Word2Vec安装、基础用法,并实现《庆余年》中文短文本相似度计算及多个案例。本专栏主要结合作者之前的博客、AI经验和相关文章及论文介绍,后面随着深入会讲解更多的Python人工智能案例及...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 24,865
精华内容 9,946
关键字:

学生对应词是什么