Obsidian
发表于|更新于|软件
|总字数:99|阅读时长:1分钟|浏览量:
插件
1.安装git插件
2.运行命令,才能在界面上显示Git
设置
打开文件的时候,显示的模式
本地图片的格式
使用 Obsidian 时,粘贴的图片路径会变成[[]]
的格式,在像 Typora 的软件上,就不适配。
可以在设置里,关闭 Wiki 链接的选项,粘贴图片后的格式就会自动变成了。
相关推荐

2022-11-19
Mybatis
教程 参考: 官方文档 源码 Provider的使用1.一定要注意type的类名.class 和 method方法名,还要注意形参也得是一样的 2.Provider的方法,大概就三个方法sql.SELECT,sql.WHERE,sql.FROM 3.SQL 对象里的方法名跟别的不一样,小写的不行,idea也识别不到,要用大写,比如SLELECT 4.Provider里返回的是String 注解 一对多查询(多个参数) 注解 一对多查询(多个参数)123456789101112131415161718192021222324252627282930313233343536373839404142@SelectProvider(method="queryPageFloors",type=BarterGroupProvider.class)@Results({ @Result(property = "userId",column="user_id"), @Result(property = "floorNum",column="floor_num"), @Result(property = "floorSecond",column="id"), @Result(property = "say",column="note"), @Result(property = "joinOrPublish",column="join_or_publish"), @Result(property="categorys",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryCategory")), @Result(property="productIds",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.queryProducts")), @Result(property="beforeResult",javaType = List.class,column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupEvaluationMapper.queryAll")), @Result(property="barterGroupUser",javaType = BarterGroupUser.class,column="{user_id=user_id,id = barter_group_id }",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.isHead")), @Result(property="futureResultNum",column="id",many=@Many(select="com.ecloud.hobay.marketing.service.infrastructure.mapper.bartergroup.BarterGroupMapper.futureResultNum"))})List<QueryFloors> queryFloors3(Page<QueryFloors> page);/** * 分类 * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_category WHERE barter_group_details_id = #{id} limit 0,7 ")List<BarterGroupCategory> queryCategory(@Param("id") Long id);/** * 产品 * @param id * @return */@Select("SELECT product_id from ecloud_marketing.barter_group_product_category WHERE barter_group_details_id = #{id} limit 0,3 ")List<Long> queryProducts(@Param("id") Long id);/** * 团员 * @param userId * @param id * @return */@Select("SELECT * FROM ecloud_marketing.barter_group_user WHERE user_id =#{user_id} and barter_group_id = #{id} and status = 1")BarterGroupUser isHead(Map<String,Object> map);@Select("SELECT count(*) from ecloud_marketing.barter_group_evaluation WHERE barter_group_details_id = #{id}")Integer futureResultNum(@Param("id") Long id); 1234567891011121314151617181920212223242526272829303132333435363738public class QueryFloors implements Serializable { @ApiModelProperty("我有的产品的id") List<ProductBarter> list; @ApiModelProperty("封装前的评论数据") private List<BarterGroupEvaluation> beforeResult; @ApiModelProperty("封装后的评论数据有分页给功能") private Page<BarterGroupEvaluation> beforeResultPage; @ApiModelProperty("封装后的评论数据") private List<BarterGroupEvaluationResult> futureResult; @ApiModelProperty("剩余评论条数") private Integer futureResultNum; @ApiModelProperty("分类") List<BarterGroupCategory> categorys; @ApiModelProperty(value="楼层数") private Integer floorNum; @ApiModelProperty(value="楼层id") private Long floorSecond; @ApiModelProperty(value="要说的") private String say; @ApiModelProperty(value="加入或者是发布") private Integer joinOrPublish; @ApiModelProperty("会员表") private BarterGroupUser barterGroupUser; @ApiModelProperty(value="加入时间") private Long joinData;} 注解 一对多查询(一个参数) 标签mybatis的xml的常用标签: include和sql标签 12345678<sql id="query_column"> id,user_no</sql><select id="test"> select <include refid="query_column"></include> from user_info</select> where标签 12345678910<select id="test"> select * from user_info where <if test="userName != null and userName != ''"> user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if></select> 如果userName= null,则sql语句就变成 1select * from user_info where and password = #{password} where标签可以去除where语句中的第一个and 或 or。 1234567891011<select id="test"> select * from user_info <where> <if test="userName != null and userName != ''"> and user_name = #{userName} </if> <if test="password != null and password != ''"> and password = #{password} </if> </where></select> set标签 123456789101112<update id="myupdate"> update user_info <set> <if test="userName != null"> user_name = #{userName}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> trim标签 where标签只能去除第一个and或or,不够灵活,trim标签功能更加强大。 trim标签的属性如下: prefix 在trim标签内容前面加上前缀 suffix 在trim标签内容后面加上后缀 prefixOverrides 移除前缀内容。即 属性会忽略通过管道分隔的文本序列,多个忽略序列用 “|” 隔开。它带来的结果就是所有在 prefixOverrides 属性中指定的内容将被移除。 suffixOverrides 移除前缀内容。 12345678<trim prefix="where" prefixOverrides="and"> <if test="userId != null"> user_id=#{userId} </if> <if test="pwd != null and pwd !=''"> user_id=#{userId} </if></trim> foreach标签 foreach元素的属性主要有item,index,collection,open,separator,close. collection 要做foreach的对象,作为入参时,List对象默认用”list”代替作为键,数组对象有”array”代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = “ids.id“,必填 item 合中元素迭代时的别名,必选 index 在list和数组中,index是元素的序号,在map中,index是元素的key,可选 open foreach代码的开始符号,一般是(和close=”)”合用。常用在in(),values()时。可选 separator 元素之间的分隔符,例如在in()的时候,separator=”,”会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样,可选。 close foreach代码的关闭符号,一般是)和open=”(“合用。常用在in(),values()时。可选 choose,when,otherwise标签 功能有点类似于Java中的 swicth - case - default 12345678910111213141516<select id="getUser" resultType="com.cat.pojo.User"> SELECT * FROM user <where> <choose> <when test="id != null and test.trim() != '' "> id = #{id} </when> <when test="name != null and name.trim() != '' "> name = #{name} </when> <otherwise> age = 17 </otherwise> </choose></where></select> if标签 123456<!-- 判断字符串--><if test="item != null and item != ''"></if><!-- 如果是Integer类型的需要把and后面去掉或是加上or--><if test="item != null"></if><if test="item != null and item != '' or item == 0"></if> 存过/函数存过 12345678<select id="pNextSupperUsers" parameterType="map" statementType="CALLABLE" resultType="vo.UserAgentVO"> { call p_next_supper_users( #{type,mode=IN,jdbcType=INTEGER}, #{userId,mode=IN,jdbcType=BIGINT} ) }</select> 123456List<UserAgentVO> pNextSupperUsers(Map<String, Object> param);Map<String, Object> param = new HashMap<>();param.put("type", 1);param.put("userId", userId);List<UserAgentVO> list = userInfoMapper.pNextSupperUsers(param); 函数 1234SELECT fn_next_user_count ( 1, u.id ) AS teamCount,FROMuser_info 源码参考 b站博学谷 架构设计 启动测试方法123456789101112131415161718192021# 第一种调用方法public static void test(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA"); sqlSession.close();}# 第二种调用方法public static void test2(){ String resource = "com/analyze/mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); Map map = userMapper.getUA(); sqlSession.close();} 下面的代码,大概意思就是能加载的配置文件的信息,解释 InputStream inputStream = Resources.getResourceAsStream(resource);这行代码的作用 读取mybatis的配置文件12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.String)public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource);}// org.apache.ibatis.io.Resources#getResourceAsStream(java.lang.ClassLoader, java.lang.String)public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) { throw new IOException("Could not find resource " + resource); } return in;}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader)public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { return getResourceAsStream(resource, getClassLoaders(classLoader));}// org.apache.ibatis.io.ClassLoaderWrapper#getClassLoadersClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader};}// org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader[])// 找到一个可以用的ClassloaderInputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // try to find the resource as passed InputStream returnValue = cl.getResourceAsStream(resource); if (null == returnValue) { returnValue = cl.getResourceAsStream("/" + resource); } if (null != returnValue) { return returnValue; } } } return null;} 下面的代码,解释SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);,用来解析全局配置文件和解析mapper文件 下面是解析全局配置文件 解析全局配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 1.创建XpathParse解析器对象,根据inputStream解析成Document对象 // 2.创建全局配置Configuration对象 // 使用构建者模式,好处降低偶尔,分离复杂对象的创建 // 构建XMLConfig XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 根据Xpath解析xml配置文件,将配置文件封装到Configuration对象 // 返回DefaultSqlSessionFactory对象 // parse() 就是配置文件解析完成了 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)// 最终返回public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.InputStream, java.lang.String, java.util.Properties)public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 点this,查看代码 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { commonConstructor(validation, variables, entityResolver); // 创建解析器 this.document = createDocument(new InputSource(inputStream));}// org.apache.ibatis.parsing.XPathParser#createDocument// 不用太关系,只是创建一个解析器的对象,顺便检查xml文档有没有写错private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(entityResolver); builder.setErrorHandler(new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { } }); // 解析器的源码了,跟mybatis没有关系了 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() { if (parsed) { // 每一个配置文件,只能解析一次 throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 从根节点开始解析,最终封装到Configuration对象 parseConfiguration(parser.evalNode("/configuration")); return configuration;}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.String)public XNode evalNode(String expression) { return evalNode(document, expression);}// org.apache.ibatis.parsing.XPathParser#evalNode(java.lang.Object, java.lang.String)public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables);}// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration// 可以配置这些信息:https://mybatis.org/mybatis-3/zh/configuration.htmlprivate void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); // 插件 pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 重点 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 重点 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 三个值是互斥的 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 专门用来解析mapper映射文件 InputStream inputStream = Resources.getUrlAsStream(url); // 重点 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } }} 解释下environment作用,就是<environment id="development">这里的,不同的环境不同的配置 1234567891011121314151617181920212223<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.3.12:3306/orangedb"/> <property name="username" value="root"/> <property name="password" value="abcd2022"/> </dataSource> </environment> </environments></configuration> 用法:如下代码 1SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"development"); 下面是解析mapper文件,配置的属性,参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html 解析mapper配置文件123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223// org.apache.ibatis.builder.xml.XMLMapperBuilder#XMLMapperBuilder(java.io.InputStream, org.apache.ibatis.session.Configuration, java.lang.String, java.util.Map<java.lang.String,org.apache.ibatis.parsing.XNode>)// 这个方法,前面已经用过了 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); }// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() { // mapper映射文件是否已经加载过 if (!configuration.isResourceLoaded(resource)) { // 从根节点解析 configurationElement(parser.evalNode("/mapper")); // 标记已经解析 configuration.addLoadedResource(resource); // 为命名空间绑定映射 bindMapperForNamespace(); } // parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) { try { // 获取命名空间 String namespace = context.getStringAttribute("namespace"); // 命名空间不能为空 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 设置当前的命名空间的值 // 构建mappedStatement对象 builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 重点 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); }}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 没有配置过getDatabaseId,所以走这里 buildStatementFromContext(list, null);}// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 没有设置过databaseId if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); // 解析Sql命令类型是什么,确实是 Select,update,insert,delete 类型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 配置语言驱动 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // 替换占位符?,保存#{}里面的内容 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); // 通过构建者助手,创建mappedstatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 封装好MappedStatement,并返回 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }// org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespacepublic String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // namespacename+点+方法名 return currentNamespace + "." + base; } 到这里,配置文件就解析完成了,下面是创建SqlSessionFactory对象 SqlSession1234 // org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);} 下面是mybatis创建sql的流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475// /Users/anthony/.m2/repository/org/mybatis/mybatis/3.5.2/mybatis-3.5.2-sources.jar!/org/apache/ibatis/builder/xml/XMLStatementBuilder.java:96// 占位符是如果进行替换的?动态sql如果进行的解析String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriverprivate LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = resolveClass(lang); } return configuration.getLanguageDriver(langClass);}// org.apache.ibatis.session.Configuration#getLanguageDriverpublic LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) { if (langClass == null) { return languageRegistry.getDefaultDriver(); } languageRegistry.register(langClass); return languageRegistry.getDriver(langClass);}// org.apache.ibatis.scripting.LanguageDriverRegistry#getDefaultDriver// 打断点可以看到是:XMLLanguageDriverpublic LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass());}public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass;}// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#XMLScriptBuilder(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化动态sql的节点处理器结合 initNodeHandlerMap();}// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#initNodeHandlerMap private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode// 解析sql,还有参数类型和结果集类型public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource;} 全部就解析完成了,接下来 看看session = sqlSessionFactory.openSession(); 创建事务对象 创建了执行器对象CasheingExecutor 创建DefaultSqlSession对象 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()@Overridepublic SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}// org.apache.ibatis.session.Configuration#getDefaultExecutorType// protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;public ExecutorType getDefaultExecutorType() { return defaultExecutorType;}// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource// 参数一:执行器,参数二:隔离级别private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获取数据源环境信息 final Environment environment = configuration.getEnvironment(); // 获取事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 获取JdbcTransaction或者ManagedTransaction // ManagedTransaction 就相当于没有事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor执行器 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(java.sql.Connection)@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit);}// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果允许缓存,会通过CachingExecutor去代理一层 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 拦截器插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }// org.apache.ibatis.executor.CachingExecutor#CachingExecutorpublic CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this);} 调用具体的API进行查询用启动类中的方法,对比可以发现两段代码不同之处为: 1234Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map map = userMapper.getUA(); 在查看DefaultSqlSession中的selectOne方法,会执行以下的调用链 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)@Overridepublic <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)// RowBounds 分页对象public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 根据传入的statementId,获取mapperStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器的方法 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)// 注意这里是CachingExecutor,默认的@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取带问号的sql语句,比如 select * from user_info where id = ? BoundSql boundSql = ms.getBoundSql(parameterObject); // 生成缓存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 二级缓存 // usermapper的标签里可以设置<Cache>缓存标签 Cache cache = ms.getCache(); if (cache != null) { // 刷新二级缓存,在<select> 标签里可以配置flushcache flushCacheIfRequired(ms); // 在<select> 标签里可以配置usecache if (ms.isUseCache() && resultHandler == null) { // 判断是不是存过 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中查询数据 List<E> list = (List<E>) tcm.getObject(cache, key); // 如果从二级缓存没有查询到数据 if (list == null) { // 委托给BaseExecutor执行 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 将查询结果保存到二级缓存中,这里只是存到map集合中,没有真正存到二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 如果配置了FlushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清空缓存 clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存中获取数据 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 如果有数据,则处理本地缓存结果给输出参数 // 还是处理存过 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 没有缓存结果,则从数据库查询结果 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list;}// org.apache.ibatis.executor.BaseExecutor#queryFromDatabaseprivate <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 向本地缓存中存入一个ExecutionPlaceholder的枚举类占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 执行查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 执行玩移除这个key localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}// org.apache.ibatis.executor.SimpleExecutor#doQuery@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取全局配置文件实例 Configuration configuration = ms.getConfiguration(); // new一个statementHandler实例 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备处理器,主要包括创建statement以及动态参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行真正的数据库操作调用 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}// org.apache.ibatis.session.Configuration#newStatementHandlerpublic StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建路由功能的StatementHanlder,根据MappedStatement中的StetementType StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件机制:对核心对象进行拦截 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}// org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandlerpublic RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }}// org.apache.ibatis.executor.SimpleExecutor#prepareStatementprivate Statement prepareStatement(StatementHandler handler, Log statementLog){ Statement stmt; // 获取代理后,增加日志功能的Connection对象 Connection connection = getConnection(statementLog); // 创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); // 参数化处理 handler.parameterize(stmt); return stmt;}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);} 解析结果1234567891011121314151617181920212223242526272829303132333435363738// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);} 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// org.apache.ibatis.executor.CachingExecutor#createCacheKey// CacheKey重新了 hacode和equals方法@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // delegate 是具体类型的执行器的应用 // 默认是SimpleExecutor return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}// org.apache.ibatis.executor.BaseExecutor#createCacheKey@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 通过构造器创建 CacheKey cacheKey = new CacheKey(); // id cacheKey.update(ms.getId()); // 分页参数 cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // sql cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 参数的值 cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // 当前环境的值 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey;} 面试题

2022-11-19
Python
安装虚拟环境12345678910111213141516171819202122232425262728293031323334# 判断有没有虚拟环境virtualenv -V# 安装虚拟环境# 需要sudopip install virtualenvpip install virtualenvwrapper# 查看有多少虚拟环境的文件夹workon# 创建虚拟环境文件夹mkvirtualenv 文件夹名字# 从虚拟文件夹退出deactiave# 进入虚拟环境中workon 虚拟环境名称# 删除虚拟环境rmvirutalenv# 查看虚拟环境有哪些框架,要在虚拟环境中执行pip freeze# 安装软件指定版本,要在虚拟环境中执行pip install flask==10.0.0.0# 导出虚拟环境中的所有扩展,要在虚拟环境中执行pip freeze > requirements.txt# 安装,要在虚拟环境中执行pip install -r requirements.txt centos7安装python3特别是在喜欢环境中已经安装的python2.x的版本中 1234567891011121314151617181920212223242526272829# 这个可能不一定要装sudo yum -y groupinstall "Development tools"# 需要的sudo yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel# 下载安装包wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0a1.tar.xz# 解压tar -xvxf Python-3.7.0a1.tar.xz# 复制文件夹mv Python-3.7.0 /usr/local# 进入到文件夹cd /usr/local/Python-3.7.0/# 编译,一定需要后面的参数./configure --prefix=/usr/local/bin/python3make & make install# 添加软连接ln -s /usr/local/bin/python3/bin/python3 /usr/bin/python3ln -s /usr/local/bin/python3/bin/pip3 /usr/bin/pip3# 验证python3pip3 基础1.注释123456789# 注释后面需要一个空格print("单行注释")print("单行注释") # 单行注释和代码之间至少要有两个空格"""多行注释"""print("这是多行注释") 2.算数运算符乘法的使用,用*可以拼接字符串 12345In [1]: "A" * 30Out[1]: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'In [2]: 1 * 30Out[2]: 30 优先级 幂 >* > / > % > // >+ > - 3.变量3.1.变量的类型数字型 整数 int 浮点 float(计算要小心) 布尔 布尔值可以用 and、or 和 not 运算 复数(用于科学技术的) 非数字型 String(字符串) List(列表) Tuple(元组) Dictionary(字典) 3.2.type函数12345678# 整数print(type(1))# 浮点数print(type(1.5))# 字符串print(type("hello world"))# 空值print(type(None)) 3.3.不同类型的变量之间的计算1234567891011121314151617181920212223242526272829In [10]: age =13In [11]: sex =TrueIn [12]: height = 180.1In [13]: age + sexOut[13]: 14In [14]: age + heightOut[14]: 193.1In [15]: age + heightOut[15]: 193.1# 字符串的拼接In [17]: first_name ="东尼"In [18]: last_name="安"In [19]: last_name+first_nameOut[19]: '安东尼'# 字符串不能和数字相加In [20]: last_name + 10---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-20-45feb354f2d0> in <module>----> 1 last_name + 10TypeError: can only concatenate str (not "int") to str 3.4.变量的输入12345678910# 可以不需要参数In [24]: input()123# 加参数,做提示In [27]: pwd = input("输入数字")输入数字123In [28]: pwd + "456"Out[28]: '123456' 3.5.数据类型转换 方法名 含义 str() 转成字符串 float() 转成浮点 int() 转成int 123456789101112131415161718192021222324# 数字和字符串相互转In [3]: age = 23In [4]: type(age)Out[4]: intIn [5]: age_str = str(age)In [6]: type(age_str)Out[6]: strIn [7]: type(int(age_str))Out[7]: int# 浮点转数值,缺失精度In [9]: pi = "3.14"In [10]: piOut[10]: '3.14'In [13]: type(float(pi))Out[13]: floatIn [15]: int(float(pi))Out[15]: 3 3.6.变量的格式化输出 符号 描述 %c 格式化字符(输出数值对应的ASCII码) %s 格式化字符串 %d 格式化整数(%06d,不足的补0) %x 格式化十六进制数(小写) %X 格式化十六进制数(大写) %o 格式化八进制数 %f 格式化浮点数字,可以指定小数点精度(%.2f) %% 输出%号 12345678910"""'我的名字是:anthony,请多多关照我的学号是:000001,请多多关照单价是:1.00,购买了2.00斤,总价是:2.000数据的比例是:20%"""print("我的名字是:%s,请多多关照" % "anthony")print("我的学号是:%06d,请多多关照" % 1)print("单价是:%.2f,购买了%.2f斤,总价是:%.3f" % (1,2,2))print("数据的比例是:%02d%%" % 20) 3.7.变量的命名 字母,和下划线和数字组成 不能以数字开始 不能与关键字重名 其余的符号都不行 区分大小写 =左右都要添加空格 两个单词之间用_ 4.条件控制4.1.if12345678910age = 15if age >= 18: print("成年了") print("在一个缩进的,是一个代码块")elif age<=18: print("没有成年")else: print("输入错误")print("结束了") 4.2.逻辑运算符123print(True and False)print(True or False)print(not True) 4.3.随机数1234In [17]: import randomIn [18]: random.randint(12,20)Out[18]: 12 4.3.while1234i = 0while i < 5: print("....") i += 1 4.4.continue 和 break如果是嵌套循环,用这个两个关键字,也只是结束当前的循环,不会影响外层的循环 5.函数5.1.函数注释1234# 这个也是可以注释的def test(): """这个也是可以注释的""" print("打印乘法表") 5.2.函数调用index.py 12def chengfabiao(): print("打印乘法表") test.py 12import indexindex.chengfabiao() 5.3.局部方法修改全局变量12345678910num =10def mo(): # 声明num是全部变量 global num num=100 print(num)mo()print(num) 5.4.多个返回值12345678def change(x,y): return y,xx =1y=2x,y = change(1,2)print(x)print(y) 5.5.缺省函数1234567891011121314151617def measure(age,gender=1): print(age) print(gender)def measure2(age,gender=1,name="anthony"): print(age) print(gender) print(name)measure(1)measure(1,2)# 有多个缺省的时候,需要指定参数名称measure2(1,name="anthonyyang",gender=2)# 拆包measure(1,*(2,3,4),**{"name":"anthony"}) 5.6.多值参数习惯存元祖的使用用*args,存字典的时候用**args 5.7.函数的参数-默认参数1234567891011121314def my_print(name,age): print(name,age)def my_print2(name,age=13): print(name,age)# 有默认值的形参,不能放在前面,会报错# def my_print3(age=13,name):# print(name,age)my_print("anthony",12)my_print2("anthony2",123)my_print2("anthony2") 5.8.函数的参数-关键字参数12345678910111213141516def my_print(name,address): print(name,address)my_print("anthony","广东")my_print("广东","anthony")my_print(name="anthony",address="广东")my_print(address="广东",name="anthony",)# ----------------**kw是关键字参数,且 hobby 就是一个 dict (字典)-------------def my_print2(name,address,**kw): if 'age' in kw: print("age=",kw["age"])my_print2("anthony","广东",age=123)my_print2("anthony","广东",kw={"age":123}) 5.8.函数的参数-只接受关键字参数123456789def my_print(name,*,address): print(name,address)# 报错# my_print("anthony","广东")# my_print("广东","anthony")my_print("anthony",address="广东")my_print(address="广东",name="anthony") 5.8.函数的参数-不定长参数hobby是可变参数,且 hobby 其实就是一个 tuple (元祖) 12345678910def print_user_info( name , age , sex = '男' , * hobby): # 打印用户信息 print('昵称:{}'.format(name) , end = ' ') print('年龄:{}'.format(age) , end = ' ') print('性别:{}'.format(sex) ,end = ' ' ) print('爱好:{}'.format(hobby)) return;# 调用 print_user_info 函数print_user_info( '两点水' ,18 , '女', '打篮球','打羽毛球','跑步 6.容器6.1.列表虽然列表可以存储不同类型的数据,但是在开发中,存储的都是相同类型数据,因为要迭代 123456789101112131415161718192021222324mylist=["a","b","c"]print(mylist)# 通过索引,访问列表中的值print(mylist[1])# 通过方括号的形式来截取列表中的数据,访问列表中的值# 就是从第 0 个开始取,取到第 2 个,但是不包含第 2 个print(mylist[0:2])# 通过索引对列表的数据项进行修改或更新mylist[1] = "bb"print(mylist)# 使用 append() 方法来添加列表项mylist.append("d")print(mylist)# 使用 del 语句来删除列表的的元素del mylist[3]print(mylist)# 列表长度print(len(mylist)) 6.2.元祖元祖用的是括号 与列表比较,元祖元素不能修改 123456789101112131415161718192021222324252627# 创建元祖方法1tuple1=('两点水','twowter','liangdianshui',123,456)tuple2='两点水','twowter','liangdianshui',123,456# 创建元祖方法2tuple3 = ()# 创建元祖方法3tuple4 = (123,)print(tuple1)print(tuple2)print(tuple3)print(tuple4)# 访问元祖print(tuple1[1])# 修改元祖的值mylist=[1,2,3]tuple5=("ddd",mylist)print(tuple5)mylist[1]=43print(tuple5)# 删除元祖,tuple 元组中的元素值是不允许删除的,但我们可以使用 del 语句来删除整个元组del tuple1 元祖和列表相互转换 123456789101112131415# 声明元祖In [54]: num_list = (1,2,3,4)In [55]: type(num_list)Out[55]: tuple# 元祖转成列表In [56]: my_list = list(num_list)# 修改值In [57]: my_list[0]=5# 再转成元祖In [58]: print(tuple(my_list))(5, 2, 3, 4) 6.2.字典列表是有序的 字典是无序的 12345678910111213141516171819202122232425262728names={"name":"xiaoming","age":"23"}# 取值print(names["name"])# 新增和修改(key存在,就是新增,不存在就是修改)names["address"] ="feilvb"names["name"] ="anthony123"print(names)# 删除names.pop("name")print(names)# 统计键值对的数量print(len(names))# 合并键值对,如果合并的时候有相同的key,那个value就是更新值temp = {"a":"b"}names.update(temp)print(names)# 遍历字典for k in names: print("遍历",k,names[k])# 清空字典names.clear() 6.3.setset可以理解为只有key的字典 12345678# 创建setset1 = set([1,2,3])# 添加元素set1.add(200)# 删除元素set1.remove(1) 6.4.字符串123456789101112131415str ="hello hello"print("字符串长度",len(str))print("字符串出现次数",str.count("llo"))print("取索引",str.index("llo"))print("取值",str[1])# 换行符,都是空白字符print("判断空白字符",str.isspace())print("是否以指定字符串开始",str.startswith("hello"))print("是否以指定字符串结束",str.endswith("LLO"))print("查找指定字符串",str.find("llo"))print("替换字符串",str.replace("hello","HELLO"))print(str[0:9:2])# bytes转字符串print(b"abcde".decode("utf-8")) 字符串前加 b:b 前缀代表的就是bytes 字符串前加 r:r/R:非转义的原始字符串 7.公共方法 内置函数: len max 只能比较字典的key min 只能比较字典的key 2.字符串,列表,元祖都可以切片 3.查看地址值 1id(str) 面向对象类名需要大驼峰命名法 1.基本语法1.1.创建对象1234567891011class Cat: def eat(self): print("小猫爱吃鱼") def drink(self): print("小猫爱喝水")tom = Cat()tom.eat()tom.drink() 1.2.对象内置方法(魔术方法)123456789101112131415161718192021222324252627class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name # 成员方法 def eat(self): print(self.name+"爱吃鱼") # 成员方法 def drink(self): print(self.name+"爱喝水") # 魔术方法 def __del__(self): print("销毁方法") # 魔术方法 def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()print(tom) 1.3.私有属性和方法1234567891011121314151617181920212223242526272829303132333435class Cat: # 构造方法 def __init__(self,name): print("初始化方法") self.name=name self.__age =18 def eat(self): print(self.name+"爱吃鱼") def drink(self): print(self.name+"爱喝水") def say_age(self): print("年纪是:"+str(self.__age)) # 调用私有方法 self.__private_method() def __private_method(self): print("私有方法") def __del__(self): print("销毁方法") def __str__(self): return "重写tostring"tom = Cat("Tom")tom.eat()tom.drink()tom.say_age()print(tom)# 这种访问方式,也是可以访问到私有的属性和方法的print(tom._Cat__age) 1.4.继承和重写123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 =100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Dog(Animal): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")# animal = Animal()# animal.eat()# animal.run()dog = Dog()dog.eat()dog.run() 1.5.多继承尽量避免使用多继承,如果继承了两个累,两个类有相同的方法和属性,容易混淆 123456789101112131415161718192021222324252627282930class Animal: def __init__(self): self.name1 = 100 self.__num2 = 200 def eat(self): print("动物吃") def run(self): print("动物跑") # 子类不允许调用私有方法 def __test(self): print("父类可以访问到私有属性和私有方法")class Zoo: def eat(self): print("动物园吃饭")class Dog(Animal, Zoo): def run(self): print("子类打印,开始调用父类方法") super().run() print("调用完父类方法")dog = Dog()dog.eat() 1.6.多态12345678910111213141516171819202122232425262728class Dog(object): def __init__(self,name): self.name = name def game(self): print("蹦蹦跳跳",self.name)class Xiaotianquan(Dog): def game(self): print("哮天犬",self.name)class Person(object): def __init__(self,name): self.name = name def game_with_dog(self,dog): print("人和狗玩耍",self.name,dog.name) dog.game()# dog = Dog("旺财")dog = Xiaotianquan("旺财")xiaoming = Person("xiaoming")xiaoming.game_with_dog(dog) 1.7.类属性和类方法和静态方法类属性 相当于静态变量 123456class Dog(object): # 类属性 age = 12 def __init__(self,name): self.name = name 类方法 123456789101112class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("静态方法",cls.age)dog = Dog()Dog.show_age() 静态方法,在不用方法类属性和静态属性的时候,可以定义成静态方法 1234567891011121314151617181920class Dog(object): # 类属性 age = 12 # 类方法 @classmethod def show_age(cls): print("类方法",cls.age) @staticmethod def static_method(): print("静态方法")dog = Dog()# 调用类方法Dog.show_age()# 调用静态方法Dog.static_method() 2.异常2.1.异常的完整语法1234567891011try: num = int(input("输入一个整数:")) 10 / numexcept ZeroDivisionError: print("请不要输入数字0")except Exception as result: print("未知错误 %s" % result)else: print("没有异常才会执行的代码")finally: print("无论是否有异常,都会异常的代码") 2.2.主动抛异常123456789101112def check(name): if(name == "anthony"): return "是安东尼" else: # 主动抛异常 raise Exception("不是安东尼")# 捕获异常try: print(check("anthony2"))except Exception as result: print(result) 3.模块导入的语法如下:[from 模块名】import [模块 1类1变量1函数1x[as别名]常用的组合形式如: import 模块名 from 模块名 import 类、变量、方法等 from 模块名 import * import 模块名 as 别名 from 模块名import 功能名 as 别名 3.1.导入模块不推荐使用, 12import pkg1import pkg2 3.2.简单的使用my_module.py 1234567title = "模块2"def say_hello(): print("i am module : %s " % title)class Cat: pass index.py 1234567import my_module# use module methodmy_module.say_hello()dog = my_module.Cat()print(dog) 3.3.导入的时候也可以起别名别名要使用大驼峰命名 1import my_module as MyModule 3.4.from…import导入一部分工具 使用的时候,就不需要写那个模块名了,直接使用 1234from my_module import say_hellofrom my_module import Catsay_hello()cat = Cat() 3.5.作为模块的正常写法123456def main(): pass# 有了这个之后,被别的模块调用的时候if __name__ = "__main__" main 3.6.包包 包含多个模块 创建一个新的文件夹,在文件夹里面创建__init__.py 123# . 是相对路径名from . import send_messagefrom . import receive_message 在文件夹里面创建两个模块 receive_message.py 12def receive(): print("接受信息") send_message.py 12def send(text): print("发送 %s" % text) 调用模块 1234import hm_messagehm_message.send_message.send('hello')hm_message.receive_message.receive() 3.7.发布模块1.创建setup.py 1234567from distutils.core import setupsetup(name="hm_message", version="1.0", description="push", py_modules=["hm_message.send_message", "hm_message.receive_message"]) 2.命令 12python setup.py buildpython setup.py sdist 3.8.安装模块123# 手动安装模块tar xxx.tar.gzpython setup.py install pymssql访问https://www.lfd.uci.edu/~gohlke/pythonlibs/#pymssql 下载[pymssql‑2.1.4‑cp38‑cp38‑win32.whl] 网络编程0.socket的历史套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。 一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯或IPC。 套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 基于文件类型的套接字家族 - 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信 基于网络类型的套接字家族 - 套接字家族的名字:AF_INET (还有AF_INET6被用于ipv6, 还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) 套接字把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的 1.udp发送端 123456789101112131415161718192021import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: send_data = str = input("输入要发送的数据") if send_data == "exit": break # udp_socket.sendto(b"这是消息",("192.169.0.1",8000)) udp_socket.sendto(send_data.encode("utf-8"),("127.0.0.1",7788)) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 接受者 1234567891011121314151617181920import socketdef main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定本地相关信息 local_addr = ("",7788) udp_socket.bind(local_addr) while True: # 等待接收对方发送的数据 recv_data = udp_socket.recvfrom(1024) print(recv_data[0].decode("gbk")) # 关闭套接字 udp_socket.close()if __name__ == '__main__': main() 2.tcp3.socket使用socket访问redis 12345678910111213141516171819import sockethost = '10.0.2.110'port = 6379buf_size = 1conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)conn.connect((host, port))cmd = 'SELECT 2\n'.encode('utf-8')r = conn.sendall(cmd)cmd = 'PING\n'.encode('utf-8')conn.sendall(cmd)while True: res = conn.recv(buf_size) print(res) if not res: breakconn.close() 服务端套接字函数 123456789101112131415161718192021222324252627282930313233s.bind() #绑定(主机,端口号)到套接字s.listen() #开始TCP监听s.accept() #被动接受TCP客户的连接,(阻塞式)等待连接的到来**客户端套接字函数**s.connect() #主动初始化TCP服务器连接s.connect_ex() #connect()函数的扩展版本,出错时返回出错码,而不是抛出异常**公共用途的套接字函数(客户端和服务端都能使用)**s.recv() #接收TCP数据s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)s.recvfrom() #接收UDP数据s.sendto() #发送UDP数据s.getpeername() #连接到当前套接字的远端的地址s.getsockname() #当前套接字的地址s.getsockopt() #返回指定套接字的参数s.setsockopt() #设置指定套接字的参数s.close() #关闭套接字 Requestpost请求参数123456789101112131415161718# 表单提交import requestsdata = { "name":"anthony", "age":"12"}requests.post(url=url,data=data)# json提交import requestsjson_data = { "name":"anthony", "age":"12"}requests.post(url=url,json=json_data) cookie操作1234cookies={ 'auther':'11223'}request.post(url=url,data=data,cookies=cookies) headers1234headers ={ 'auth':'123'}request.post(url=url,data=data,cookies=cookies,headers=headers) 请求超时12# 如果超过2s就超时报错requests.post(url=url,timeout=2) 鉴权有些页面,需要,比如spring secuity的页面 1requests.post(url=url,timeout=2,auth=('anthony','123456')) 编码12# 先编码 再解码r.text.encode('utf-8').decode(unicode_escape) 下载流21234567url = "http://wx4.sinaimg.cn/large/d030806aly1fq1vn8j0ajj21ho28bduy.jpg"rsp = requests.get(url, stream=True)with open('1.jpg', 'wb') as f: # 边下载边存硬盘, chunk_size 可以自由调整为可以更好地适合您的用例的数字 for i in rsp.iter_content(chunk_size=1024): f.write(i) requests.get(url)默认是下载在内存中的,下载完成才存到硬盘上 Response.iter_content来边下载边存硬盘 Flask路由入门12345678910from flask import Flaskapp = Flask(__name__)@app.route('/login')def hello_world(): return 'Hello World!'if __name__ == '__main__': app.run() URL上动态参数1234567891011121314from flask import Flaskapp = Flask(__name__)@app.route("/")def hello_world(): return "hello world"@app.route("/<int:age>")def play_game(age): return "hello world"+str(age)if __name__ == '__main__': app.run(debug=True) 自定义转换器1234567891011121314151617181920212223242526from flask import Flaskfrom werkzeug.routing import BaseConverterapp = Flask(__name__)# 自定义,并且继承BaseConverterclass MyConverter(BaseConverter): def __init__(self, map, regex): # map 就是app.url_map 也就是请求路径 # regex,就是d{3}, 也就是自定义的规则 super(MyConverter, self).__init__(map) self.regex = regexapp.url_map.converters["haha"] = [email protected]('/<haha("\d{3}"):age>')def play_game2(age): return "自定义转换器,接收3位" + str(age)@app.route('/<haha("\d{4}"):age>')def play_game3(age): return "自定义转换器,接收4位" + str(age)if __name__ == '__main__': app.run(debug=True) 给路由添加请求方式12345678910from flask import Flaskapp = Flask(__name__)@app.route('/',methods=['POST'])def play_game2(age): return "自定义转换器,接收3位" + str(age)if __name__ == '__main__': app.run(debug=True) 返回响应体123456789101112131415161718192021from flask import Flaskapp = Flask(__name__)@app.route('/')def method1(): """直接返回字符串信息""" return "返回字符串信息"@app.route('/2')def method2(): """直接返回字符串信息""" return "返回字符串信息",[email protected]('/3')def method3(): """直接返回字符串信息""" return {"name":"anthony"},200,{"Content-Type":"application/json","token":"123456"}if __name__ == '__main__': app.run(debug=True) 重定向12345678910from flask import Flask,redirectapp = Flask(__name__)@app.route('/')def method1(): return redirect("http://baidu.com")if __name__ == '__main__': app.run(debug=True) 跳转页面123456789101112131415161718192021222324from flask import Flask, render_template, request,redirectapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": # return render_template("login.html", **{"msg": "登录成功"}) return redirect("/index") else: return render_template("login.html", **{"msg": "用户名或者密码错误"})@app.route("/index")def index(): return "欢迎登录"if __name__ == '__main__': app.run() 异常捕获123456789101112131415161718from flask import Flask,abortapp = Flask(__name__)@app.route('/')def method1(): """abort 异常抛出""" abort(404) return "hello world""""捕获异常"""@app.errorhandler(404)def method1(e): print(e) return "页面找不到"if __name__ == '__main__': app.run(debug=True) 获取请求参数Form12345678910111213141516171819from flask import Flask, render_template, requestapp = Flask(__name__)@app.route('/login', methods=['GET', 'POST'])def hello_world(): print("请求来了") # 获取post传过来的值 user = request.form.get("user") pwd = request.form.get("pwd") if user == "anthony" and pwd == "123456": return render_template("login.html", **{"msg": "登录成功"}) else: return render_template("login.html", **{"msg": "用户名或者密码错误"})if __name__ == '__main__': app.run() 获取请求参数Body123456789101112from flask import Flask, requestapp = Flask(__name__)@app.route('/callback')def hello_world(): data = request.get_data() print(data) return 'Hello, World!'if __name__ == "__main__": app.run(debug=True, port=10086) 环境变量/配置文件12345678910111213141516171819202122from flask import Flask,redirect,requestapp = Flask(__name__)# 1.从配置类中加载class MyConfig(object): DEBUG =True# app.config.from_object(MyConfig)# 2.从配置文件中加载# app.config.from_pyfile("Config.ini")# 在项目的根目录创建个Config.ini文件# 3.从环境变量# app.config.from_envvar("")@app.route('/')def method1(): passif __name__ == '__main__': app.run() 钩子,类似拦截器12345678910111213141516171819202122232425262728293031from flask import Flask,redirect,requestapp = Flask(__name__)@app.before_first_requestdef before_first_request(): """只请求一次""" print("before_first_request")@app.before_requestdef before_request(): """每次都会请求""" print("before_request")@app.after_requestdef after_request(resp): """比如做json统一的返回格式""" print("after_request") return [email protected]_requestdef teardown_request(e): """最后会请求到这里,适合做异常信息统计""" print("teardown_request")@app.route('/')def method1(): return "hello world"if __name__ == '__main__': app.run(debug=True) 视图内容和模板123456789101112131415161718192021from flask import Flask, make_response, requestapp = Flask(__name__)# 设置[email protected]("/set_cookie")def set_cookie(): response = make_response("set cookie") response.set_cookie("computer","macbook pro") response.set_cookie("age","13 pro",1000) return [email protected]("/get_cookie")def get_cookie(): name = request.cookies.get("computer") age = request.cookies.get("age") return "name:%s,age:%s"%(name,age)if __name__ == '__main__': app.run(debug=True) Session1234567891011121314151617181920from flask import Flask, make_response, request, sessionapp = Flask(__name__)app.config["SECRET_KEY"]="123456"# 设置[email protected]("/set_session/<path:name>")def set_session(name): session["name"] =name return "set session"@app.route("/get_session")def get_session(): value = session.get("name") return "value:%s"%(value)if __name__ == '__main__': app.run(debug=True) orm入门12pip install flask_sqlalchemypip install pymysql 1234567891011121314151617181920212223242526272829303132333435363738from flask import Flask,render_templatefrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)# 设置数据库的配置信息app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://Rong:[email protected]:3306/data36"app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False# 创建sqlAlchemy对象,关联appdb = SQLAlchemy(app)# 创建模型类class Role(db.Model): __tablename__ = "roles" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32))class Users(db.Model): __tablename__ = "users" id =db.Column(db.Integer,primary_key=True) name = db.Column(db.String(32)) # 建立外键 role_id =db.Column(db.Integer,db.ForeignKey(Role.id))@app.route("/")def demo(): return "response"if __name__ == '__main__': # 创建数据库的表 db.drop_all() db.create_all() app.run(debug=True) 蓝图基本使用demo01.py 12345678910from flask import Flask, Blueprintfrom demo02 import blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(blue)if __name__ == '__main__': app.run(debug=True) demo02.py 123456789101112from flask import Blueprint# 创建蓝图对象blue = Blueprint("my_blue", __name__)@blue.route("/")def demo(): return "response"@blue.route("/2")def demo2(): return "response2" 蓝图包的使用目录结构:|—demo.py|—-|user||—-|user|–__init__.py|—-|user|–views.py demo.py 123456789101112from flask import Flaskfrom user import user_blueapp = Flask(__name__)# 讲蓝图注册到app中app.register_blueprint(user_blue)if __name__ == '__main__': print(app.url_map) app.run(debug=True) init.py 123456from flask import Blueprint# 创建蓝图对象user_blue = Blueprint("user", __name__)from user import views views.py 123456789from user import user_blue@user_blue.route("/")def demo(): return "response"@user_blue.route("/2")def demo2(): return "response2" Django命令12345678# 安装djangopip install django# 生成数据库 迁移文件python manage.py makemigrations# 执行迁移生成表python manage.py migrate PyCharm和Django-admin创建的项目不一样12345678910111213# 项目创建django-admin startproject 项目名# 结构项目名|----|manage.py [项目管理,启动项目,创建app,数据管理]|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py [项目配置]|----|----|settings.py [路由]|----|----|urls.py [接收网路请求,异步]|----|----|wsgi.py [接收网路请求,同步] 12345678910# 用PyCharm创建的目录结构项目名|----|manage.py |----|templates文件夹|----|项目名同名文件夹|----|----|__init__.py|----|----|asgi.py|----|----|settings.py|----|----|urls.py|----|----|wsgi.py PyCharm创建的根目录下有一个templates目录Django-admin 是没有的,要在app下的目录创建templates settings.py 1234567891011121314151617181920212223242526272829303132333435# PyCharm创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },]# 命令行创建的TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, },] 创建应用123|项目||----|app,后台系统[独立的表结构,模板,不相互影响]|----|app,前台系统[独立的表结构,模板,不相互影响] 123456789101112131415161718192021# 创建apppython manage.py startapp app名字djangoProject├── app01 [刚创建的app]│ ├── __init__.py│ ├── admin.py [django默认admin后台,不用动]│ ├── apps.py [app启动类,不用动]│ ├── migrations [数据库的迁移的,数据库变更记录]│ │ └── __init__.py│ ├── models.py [重要,对数据库进行操作]│ ├── tests.py [单元测试]│ └── views.py [重要,函数]├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates 注册APP项目名→项目名同名文件夹→settings.py 12345678910INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 添加下面一行的配置,下面这个是怎么来的呢?看下面 'app01.apps.App01Config' ] 项目名→APP文件夹→apps.py 12345from django.apps import AppConfigclass App01Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'app01' 编写URL和视图函数对应关系编辑urls.py 在app文件夹下的views.py 1234567from django.http import HttpResponsefrom django.shortcuts import render# Create your views here.# request 必须要的def index(request): return HttpResponse("欢迎使用") 启动12# 命令行启动python ./manage.py runserver templates模板 静态资源12345678910111213141516171819202122232425djangoProject├── app01│ ├── __init__.py│ ├── admin.py│ ├── apps.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── static [手动创建的]│ │ ├── css [手动创建的] │ │ ├── img [手动创建的]│ │ │ └── 1.png│ │ └── js│ ├── tests.py│ └── views.py├── db.sqlite3├── djangoProject│ ├── __init__.py│ ├── asgi.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── manage.py└── templates └── user_list.html 12345678910111213141516171819{% load static %}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>用户列表</title></head><body><h1>用户列表</h1>加载静态资源写法一:<img src="/static/img/1.png">写法二:<img src="{% static 'img/1.png' %}"></body></html> 数据库主意:app需要先注册 12345python ./manage.py makemigrationspython ./manage.py migrate# Pycharm->tools->Run manager.py Task 开启,可以替代python manager.py 这个前缀 1pip3 install pymysql 12345678910111213141516171819from pymysql import connectcon = connect(host="43.240.30.52", port=3306, user="yanganli_github", password="yanganli_github", database="yanganli_github", charset="utf8")cursor = con.cursor()# 查询出条数print(cursor.execute("select * from post"))for one in cursor.fetchall(): print(one)cursor.close()con.close() 自动化测试安装和命令1234567891011121314# 安装pytestpip install pytest# 安装python-html包,生成html报告pip install pytest-html# 生成allure报告pip install allure-pytest# -s 打印printpytest -s test_auth.py# 只测试这一个文件pytest -q -s test_auth.py Pycharm识别pytest 规范测试文件已test_开头 测试类已Test开头,并且不能带有init方法 测试函数以test_开头 断言基本使用assert 生成报告123456789101112# 生成html'--html=../report/report.html'# 生成xml格式的报告'--junitxml=./report/report.xml'# 生成allure报告'--alluredir','../report/reportallure'# 生成allure报告,在report的文件夹执行pytest.main(['../test_case/','--alluredir','../report/allure_raw/'])./allure serve /myitem/autotest/report/allure_raw Locust1234567pip install locust# centos需要添加环境变量# 如果locust在哪个目录,如果python安装在这个目录:/usr/local/bin/python3ln -s /usr/local/bin/python3/bin/locust /usr/bin/locustlocust --version 快速入门新建一个redis.py 12345678910111213141516class QuickstartUser(HttpUser): wait_time = between(1, 10) @task def index_page(self): self.client.get("/hello") self.client.get("/world") @task(3) def view_item(self): for item_id in range(10): self.client.get(f"/item?id={item_id}", name="/item") time.sleep(1) def on_start(self): self.client.post("/login", json={"username": "foo", "password": "bar"}) 在文件目录,执行 123locust -f redis.py# 启动成功后,访问 http://localhost:8089 写一个Locust文件Locust文件是一个普通的python文件,唯一的要求就是至少有一个类继承的User类 User一个User类标识一个用户,Locust会给每个用户模拟一个User实例,公共的属性通常在User类里被定义 wait_time 属性用户的wwait_time方法通常作用于执行的任务简直等待多长时间 这里有三个内建的等待时间的函数 constant 一段固定的时间 between 最小和最大数之间的随机时间 constant_pacing 确保任务运行一次最多不超过x秒的适应时间 举个例子,让每个用户在每个任务执行之间等待0.5s到10s 12345678from locust import User, task, betweenclass MyUser(User): @task def my_task(self): print("executing my_task") wait_time = between(0.5, 10) 也可以直接在你的类里声明wait_time方法,举个例子,下面的User类,会睡眠1s,2s… 12345678class MyUser(User): last_wait_time = 0 def wait_time(self): self.last_wait_time += 1 return self.last_wait_time ... weight 属性如果文件中存在多个用户类,并且在命令行中没有指定用户类,则Locust将生成每个用户类的相同数量。您还可以通过将用户类作为命令行参数传递来指定从同一个locustfile中使用哪些用户类 1locust -f locust_file.py WebUser MobileUser 如果希望模拟某种类型的更多用户,可以在这些类上设置权重属性,比方说,web用户是移动用户的三倍多 1234567class WebUser(User): weight = 3 ...class MobileUser(User): weight = 1 ... host 属性host属性是host的URL前缀,通常,当locust启动的时候,在locust的web界面或者在命令上中使用--hosts选项中使用 如果一个类声明了声明的host属性,它将在没有使用--host命令行或web请求仲使用 tasks属性用户类可以使用@task装饰器,将任务声明为方法,但是也可以使用下面详细描述的tasks属性来指定任务。 environment 属性对用户正在其中运行的环境的引用,使用它影响这环境,或者是运行着在它的容器中,比如去停止运行从任务方法中 1self.environment.runner.quit() 如果运行的是独立的locust实例,它将停止全部,如果是运行在工作节点,它将停止节点 on_start 和 on_stop 方法用户 或者是任务集合可以声明``on_start方法或者on_stop方法,用户将调用它自己的on_start方法在它将要开始运行的时候,on_stop方法,将在停止运行的时候调用,比如TaskSet,on_start方法被调用在模拟的用户开始执行任务,on_stop方法在停止模拟的用户执行任务的时候调用(或者被interrupt()`方法调用,或者是用被用杀掉) Tasks当启动负载测试,一个用户实例将被创建, 下载mp4 12345678910111213141516171819202122232425262728293031323334353637383940414243import osimport timeimport requestsfrom tqdm import tqdm # 进度条模块def down_from_url(url, dst): # 设置stream=True参数读取大文件 response = requests.get(url, stream=True) # 通过header的content-length属性可以获取文件的总容量 file_size = int(response.headers['content-length']) if os.path.exists(dst): # 获取本地已经下载的部分文件的容量,方便继续下载,如果不存在就从头开始下载。 first_byte = os.path.getsize(dst) else: first_byte = 0 # 如果大于或者等于则表示已经下载完成,否则继续 if first_byte >= file_size: return file_size header = {"Range": f"bytes={first_byte}-{file_size}"} pbar = tqdm(total=file_size, initial=first_byte, unit='B', unit_scale=True, desc=dst) req = requests.get(url, headers=header, stream=True) with open(dst, 'ab') as f: # 每次读取一个1024个字节 for chunk in req.iter_content(chunk_size=1024): if chunk: f.write(chunk) pbar.update(1024) pbar.close() return file_sizeif __name__ == '__main__': url = input("请输入.mp4格式的视频链接地址,按回车键确认") # 根据时间戳生成文件名 down_from_url(url, str(time.time()) + ".mp4") SSH下载服务器文件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import paramikoclass LinuxFile: def __init__(self, ip, port, username, password): try: self.ip = ip self.port = port self.username = username self.password = password self.transport = paramiko.Transport((str(self.ip), int(self.port))) self.transport.connect(username=self.username, password=self.password) self.sftp = paramiko.SFTPClient.from_transport(self.transport) except Exception as e: raise e def up_file(self, localhost_file, server_file): """ 将本地文件上传至服务器 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.put(localhost_file, server_file) def down_file(self, localhost_file, server_file): """ 将服务器文件下载至本地 :param localhost_file: 本地文件路径 :param server_file: 服务器保存路径 :return: """ self.sftp.get(localhost_file, server_file) def close(self): """ 关闭服务器 :return: """ self.transport.close()if __name__ == '__main__': test = LinuxFile('47.242.218.75', '22', 'root', 'Qwer1234') # test.up_file('../2020-10-11_20-21-28.py', '/root/2020-10-11_20-21-28.py') test.down_file('/var/log/nginx/access.log','a.log')

2023-07-02
Rime
基本概念Rime 是一个输入法框架,并不是狭义上的“输入法”,而是将各种输入法的共性抽象出来的算法框架。通过不同的配置文件,Rime 可以支持多种输入方案(Schema),这个所谓的输入方案就是我们狭义上的“输入法”。比如朙月拼音输入法就是 Rime 自带的一种输入方案,另外还有比如四叶草输入法(https://github.com/fkxxyz/rime-cloverpinyin)等等。鼠须管、小狼毫、中州韵分别是 Rime 在不同操作系统下的实现程序。Rime 的配置、词库文件均使用文本方式,便于修改。所有文件均要求以 UTF-8 编码。在配置文件中,以 # 号开头表示注释。 配置文件所在的目录Rime 有两个重要的配置目录: 共享配置目录 【中州韻】 /usr/share/rime-data/ 【小狼毫】 "安裝目錄\data" 【鼠鬚管】 "/Library/Input Methods/Squirrel.app/Contents/SharedSupport/" 用户配置目录 【中州韻】 ~/.config/ibus/rime/ (0.9.1 以下版本爲 ~/.ibus/rime/) 【小狼毫】 %APPDATA%\Rime 【鼠鬚管】 ~/Library/Rime/ 共享目录下放置的是 Rime 的预设配置,在软件版本更新时候,也会自动更新该目录下的文件。所以请不要修改该目录下的文件。 用户目录则放置用户自定义的配置文件。我们要做的修改都放在用户目录下。 对于鼠须管而言,用户目录初始时只有如下几个文件。 installion.yaml 文件记录的是当前 Rime 程序的版本信息。其中有一个字段 installation_id 用来在同步用户词典时唯一标记当前 Rime 程序。 user.yaml 文件记录用户的使用状态。比如上次“重新部署”的时间戳,上次选择的输入方案等。 build 目录下放的是每次“重新部署”后生成的文件。包括字典文件编译后生成的「.bin」文件,包括与自定义配置合并后生成的各种 yaml 配置文件。 xxx.userdb 目录下放的是对应输入方案的用户词典。即用户在使用时候选择的词组、词频等动态信息,这个目录是实时更新的。 sync 目录是用来做用户数据同步的。每个 sync/installation_id 目录对应不同电脑上的 Rime 程序的用户数据。(如果你由多台电脑安装了 Rime,并设置了同步。)按照作者的说法,Rime 的用户词典同步原理是: 手工从其他电脑复制或者从网盘自动同步 ⇒ sync/*/*.userdb.txt ⇒ 合并到本地 *.userdb ⇒ 导出到 sync/<installation_id>/*.userdb.txt。 关于调试Rime 的日志目录放在如下为止: 【中州韻】 /tmp/rime.ibus.* 【小狼毫】 %TEMP%\rime.weasel.* 【鼠鬚管】 $TMPDIR/rime.squirrel.* 早期版本 用户配置目录/rime.log 修改配置如果想要修改配置,请不要直接修改原有的 xxx.yaml 文件,而是应该新建一份 xxx.custom.yaml 文件,其中 xxx 与原文件名相同。 在 .custom.yaml 文件中对于要修改的配置项,都需要放在 patch 根节点下面。 每次修改配置,都需要在鼠须管的菜单中选择“重新部署”后才能生效。 修改候选词个数Rime 默认每次出现的候选词个数为 5 个,我们可以将其修改为 1~9 之间的任意数。 在用户目录下新建一个 default.custom.yaml 文件(default.yaml 文件可以在共享配置目录下找到),写入如下内容: 123456789patch: "menu/page_size": 9 # 字段名加不加双引号都可以 # 或者是這樣patch: menu: page_size: 8# 但是注意这种写法是覆盖整个 menu 字段。好在默认情况下,menu 下一级也只有 page_size 字段,如果是有多个下级字段,请不要这么写。 上面的 default 文件是修改所有输入方案的候选词个数,如果只想针对某个输入方案做调整,比如对于朙月输入方案,那么只需要在用户目录下建立 luna_pinyin.custom.yaml 文件并写入如上内容,再重新部署即可。(注意,对输入方案定义文件 xxx.schema.yaml 的修改,新建的文件名只需要是 xxx.custom.yaml,并不需要加上 schema,写成 xxx.schema.custom.yaml 这样。) 使用,使用快捷键F4,然后像拼音打字选候选字一样,选择就好 输入方案的可切换状态,请参考后续的 switches 章节 主题鼠须管的外观配置文件是 squirrel.yaml(小狼毫的外观配置文件是 weasel.yaml)。所以我们需要在用户配置目录下新建一个 squirrel.custom.yaml,参考或者拷贝开源项目的写法,通常有很多个主题,我们可以通过 style/color_scheme 来选择一个主题 开源项目的主题文件 参考: 鼠须管输入法配置 - 哈呜.王 (hawu.me) Rime Squirrel 鼠须管输入法配置详解 - 知乎 (zhihu.com)

2023-07-08
Telegram Bot
机器人设置消息分类消息分两种: 普通文本消息(比如test) 命令消息(以'/'开头的文本,比如 /test ) 默认在群里只能收到命令消息,和机器人私聊可以接收到全部的消息,如果想让机器人在群里也能接收到全部的消息,操作如下访问BotFather,输入/setprivacy,选择自己的机器人,选择DISABLED,就可以了

2024-02-26
Vue后台系统
项目由 Vue3 + Pina + JavaScript + Ant Design Vue 创建项目12345# 直接创建npm create vite@latest my-vue-app -- --template vue# 如果要创建ts或者自定义配置的项目就用这个npm create vite 引入Ant Design Vue1npm i --save [email protected] mian.js 删掉自带的style.css文件 12345678import {createApp} from 'vue'import Antd from 'ant-design-vue';import 'ant-design-vue/dist/reset.css';import App from './App.vue'const app = createApp(App);app.use(Antd)app.mount('#app'); 安装router1npm install vue-router@4 创建src/router/index.js文件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546import { createWebHistory, createRouter } from 'vue-router'/** * Note: 路由配置项 * * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 * name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 * roles: ['admin', 'common'] // 访问路由的角色权限 * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 * meta : { noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false) title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 } */// 公共路由export const constantRoutes = [ { path: '/login', component: () => import('@/views/login'), hidden: true },]const router = createRouter({ history: createWebHistory(), routes: constantRoutes, scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { top: 0 } } },});export default router; 配置开发环境编辑vite.config.js 123456789101112131415161718192021import { defineConfig, loadEnv } from 'vite'import path from 'path'import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/export default defineConfig(({ mode, command }) => { return { plugins: [vue()], resolve: { // https://cn.vitejs.dev/config/#resolve-alias alias: { // 设置路径 '~': path.resolve(__dirname, './'), // 设置别名 '@': path.resolve(__dirname, './src') }, // https://cn.vitejs.dev/config/#resolve-extensions extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] } }}) 项目根目录创建环境配置文件 .env.development .env.prod 12345678# 页面标题VITE_APP_TITLE = 若依管理系统# 开发环境配置VITE_APP_ENV = 'development'# 若依管理系统/开发环境VITE_APP_BASE_API = '/dev-api' 安装sass12345npm install -D sass# 直接使用就可以<style lang="scss" scoped></style> 全局样式创建src/style/index.scss文件 123456789101112131415161718@import './demo.scss';html { height: 100vh; box-sizing: border-box;}body { height: 100vh; margin: 0; //-moz-osx-font-smoothing: grayscale; //-webkit-font-smoothing: antialiased; //text-rendering: optimizeLegibility; //font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;}#app { height: 100vh;} 创建src/style/demo.scss文件 1// 这个文件是用来写css的,前面导入的这个文件,所以后面只需要写就行了 导入scss,编辑main.js 1import './style/index.scss' Ant Design Vue Icon的使用123456789101112131415<template> <a-form class="login-from"> <a-form-item size> <a-input placeholder="请输入用户名" size="large"> <template #prefix> <UserOutlined/> </template> </a-input> </a-form-item> </a-form></template><script setup> import {UserOutlined, LockOutlined} from '@ant-design/icons-vue'</script> 安装Axios1npm install axios 创建 src/utils/request.js 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import axios from 'axios'axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'// 创建axios实例const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: import.meta.env.VITE_APP_BASE_API, // 超时 timeout: 10000})// request拦截器service.interceptors.request.use(config => { return config}, error => { console.log(error) Promise.reject(error)})// 响应拦截器service.interceptors.response.use(res => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; if (code === 401) { return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { ElMessage({ message: msg, type: 'error' }) return Promise.reject(new Error(msg)) } else if (code === 601) { ElMessage({ message: msg, type: 'warning' }) return Promise.reject(new Error(msg)) } else if (code !== 200) { ElNotification.error({ title: msg }) return Promise.reject('error') } else { return Promise.resolve(res.data) } }, error => { console.log('err' + error) let { message } = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { message = "系统接口请求超时"; } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) })export default service main.js 1import router from './router'

2022-11-19
Element-UI
表单验证简单版123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" ref="form"> <el-form-item label="用户名" prop="username" :rules="[ {required:true,message:'请输入用户名',trigger:'blur'}, {min:6,max:12,message:'长度在6-12位字符',trigger:'blur'}, ]"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password" :rules="[ {required:true,message:'请输入密码',trigger:'blur'}, {min:6,max:12,message:'长度在6-12位字符',trigger:'blur'}, ]"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>export default { data() { return { form: { username: "", password: "" } } }, methods:{ login(form){ this.$refs[form].validate((valid)=>{ if(valid){ console.log("验证通过") this.axios.post("http://localhost",this.form).then( res=>{ console.log(res) if(res.data.status === 200){ localStorage.setItem("username",res.data.username) this.$message({ message:res.data.message, type:'success' }) this.$router.push('/home') } } ) .catch(err=>{ console.error(err) }) }else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 自定义校验123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" :rules="rules" ref="form"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>export default { data() { // 验证用户名 const validateName = (rule, value, callback) => { if (value === '') { callback(new Error("请输入用户名")) } else if (value === '123456') { console.log("11111111111") callback(new Error('换个用户名,猜到啦')) } else { callback() } } const validatePassword = (rule, value, callback) => { if (value === '') { callback(new Error("请输入密码")) } else if (!value === '123456') { callback(new Error('换个密码,猜到啦')) } else { callback() } } return { form: { username: "", password: "" }, rules: { username: [{validator: validateName, trigger: 'blur'}], password: [{validator: validatePassword, trigger: 'blur'}], } } }, methods: { login(form) { this.$refs[form].validate((valid) => { if (valid) { console.log("验证通过") // this.axios.post("http://localhost", this.form).then( // res => { // console.log(res) // if (res.data.status === 200) { // localStorage.setItem("username", res.data.username) // this.$message({ // message: res.data.message, // type: 'success' // }) // this.$router.push('/home') // } // } // ) // .catch(err => { // console.error(err) // }) } else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 自定义校验封装新建一个vaildate.js 123456789101112131415161718192021// 用户名匹配export function nameRule(rule,value,callback){ if (value === '') { callback(new Error("请输入用户名")) } else if (value === '123456') { console.log("11111111111") callback(new Error('换个用户名,猜到啦')) } else { callback() }}export function passwordRule(rule,value,callback){ if (value === '') { callback(new Error("请输入密码")) } else if (value === '123456') { callback(new Error('换个密码,猜到啦')) } else { callback() }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<template> <div class="login"> <el-card class="box-card"> <div slot="header" class="clearfix"> <span>通用后台管理系统</span> </div> <el-form label-width="80px" :model="form" :rules="rules" ref="form"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login('form')">登录</el-button> </el-form-item> </el-form> </el-card> </div></template><script>import {nameRule,passwordRule} from "@/utils/vaildate";export default { data() { return { form: { username: "", password: "" }, rules: { username: [{validator: nameRule, trigger: 'blur'}], password: [{validator: passwordRule, trigger: 'blur'}], } } }, methods: { login(form) { this.$refs[form].validate((valid) => { if (valid) { console.log("验证通过") // this.axios.post("http://localhost", this.form).then( // res => { // console.log(res) // if (res.data.status === 200) { // localStorage.setItem("username", res.data.username) // this.$message({ // message: res.data.message, // type: 'success' // }) // this.$router.push('/home') // } // } // ) // .catch(err => { // console.error(err) // }) } else { console.error("验证不通过") } }) } }}</script><style scoped lang="scss">.login { width: 100%; height: 100%; position: absolute; background: cadetblue; .box-card { width: 450px; margin: 200px auto; .el-button { width: 100%; } }}</style> 遍历路由渲染菜单栏src>common>menu.vue 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869<template> <div class="menu"> <el-aside width="200px"> <el-menu router default-active="2" class="el-menu-vertical-demo" background-color="cornflowerblue" text-color="#fff" active-text-color="#ffd04b"> <template v-for="(item,index) in menus"> <div> <el-submenu :index="index + '' " :key="index" v-if="!item.hidden"> <template slot="title"> <i :class="item.iconClass"></i> <span>{{ item.name }}</span> </template> <el-menu-item-group v-for="(child,index) in item.children" :key="index"> <el-menu-item :index="child.path"> <i :class="child.iconClass"></i> {{ child.name }} </el-menu-item> </el-menu-item-group> </el-submenu> </div> </template> </el-menu> </el-aside> </div></template><script>export default { data(){ return{ menus:[] } }, created() { console.log(this.$router.options.routes) this.menus=this.$router.options.routes }, methods: { }}</script><style scoped lang="scss">.menu{ .el-aside{ height: 100%; .el-menu{ height: 100%; .fa{ margin-right: 10px; } } .el-submenu .el-menu-item{ min-width: 0; } }}</style> route.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136import Vue from 'vue'import Router from 'vue-router'// import Home from '../components/Home.vue'Vue.use(Router)export default new Router({ routes:[ { path:'/', // 重定向 redirect:'/login', // 路由懒加载 hidden:true, component:()=>import('@/components/Login.vue') // 异步组件 }, { path:'/login', name:'Login', hidden:true, // 路由懒加载 component:()=>import('@/components/Login.vue') // 异步组件 }, { path:'/home', // component:Home hidden:true, // 路由懒加载 component:()=>import('@/components/Home.vue') // 异步组件 }, { path:'/home2', // component:Home hidden:true, // 路由懒加载 component:resolve =>require(['@/components/Home.vue'],resolve) // 异步组件 }, { path:'*', hidden:true, // 路由懒加载 component:()=>import('@/components/404.vue') }, { path:"/home", name:"学生管理", iconClass:'fa fa-user', // 默认重定向 redirect:'/home/student', component:()=>import('@/components/Home.vue'), children:[ { path:'/home/student', name:'学生列表', iconClass:"fa fa-list", component:()=>import("@/components/students/StudentList.vue") }, { path:'/home/info', name:'信息列表', iconClass:"fa fa-list", component:()=>import("@/components/students/InfoList.vue") }, { path:'/home/infos', name:'信息管理', iconClass:"fa fa-list-alt", component:()=>import("@/components/students/InfoLists.vue") }, { path:'/home/work', name:'作业列表', iconClass:"fa fa-list-ul", component:()=>import("@/components/students/WorkList.vue") }, { path:'/home/workd', name:'作业管理', iconClass:"fa fa-th-list", component:()=>import("@/components/students/WorkMent.vue") } ] }, { path:"/home", name:"数据分析", iconClass:'fa fa-bar-chart', component:()=>import('@/components/Home.vue'), children:[ { path:'/home/dataview', name:'数据概览', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/DataView.vue") }, { path:'/home/mapview', name:'地图概览', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/MapView.vue") }, { path:'/home/travel', name:'信息管理', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/TraveMap.vue") }, { path:'/home/score', name:'分数地图', iconClass:"fa fa-list-alt", component:()=>import("@/components/dataAnalysis/ScoreMap.vue") } ] }, { path:"/users", name:"用户中心", iconClass:'fa fa-user', component:()=>import('@/components/Home.vue'), children:[ { path:'/users/user', name:'权限管理', iconClass:"fa fa-user", component:()=>import("@/components/users/User.vue") } ] }, ], mode:'history'}) 面包屑的使用不同的路由,面包屑显示的信息会自动变化bread.vue 12345678910111213141516171819202122<template> <div> <el-card> <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> <el-breadcrumb-item v-for="(item,index) in $route.matched" :key="index" >{{item.name}}</el-breadcrumb-item> </el-breadcrumb> </el-card> </div></template><script>export default {}</script><style scoped></style> home.vue 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859<template> <div class="home"> <Header></Header> <el-container class="content"> <Menu>menu</Menu> <el-container> <el-main> <Breadcrumb/> <div class="cont"> <router-view></router-view> </div> </el-main> <el-footer> <Footer></Footer> </el-footer> </el-container> </el-container> </div></template><script>import Header from "@/components/common/Header.vue";import Footer from "@/components/common/Footer.vue";import Menu from "@/components/common/Menu.vue";import Breadcrumb from '@/components/common/Breadcrumb.vue'export default { components: { Header, Footer, Breadcrumb, Menu, }, data() { return {} }}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped lang="less">.home { width: 100%; height: 100%; .content{ position: absolute; width: 100%; top: 60px; bottom: 0; .cont{ margin: 20px 0; } }}</style> Card 卡片12345<!-- body-style的使用 --><el-card :body-style="{padding:'0px'}"> <div style="padding-top: 4px;display: flex"> </div</div></el-card> 级联选择器新版本props的使用级联选择器太高可以在全局样式里给.el-cascader-panel设置高度为200px:props=”{ expandTrigger: ‘hover’, value: ‘cat_id’, label: ‘cat_name’, children: ‘children’ }” 多选框 1234567891011121314151617<div> <el-checkbox-group v-model="checkboxGroup1"> <el-checkbox-button v-for="city in cities" :label="city" :key="city">{{city}}</el-checkbox-button> </el-checkbox-group></div>const cityOptions = ['上海', '北京', '广州', '深圳']; export default { data () { return { checkboxGroup1: ['上海'], checkboxGroup2: ['上海'], checkboxGroup3: ['上海'], checkboxGroup4: ['上海'], cities: cityOptions }; } Avatar 头像12<el-avatar shape="square" size="small" src="https://baidu.com/logo"></el-avatar> Table 表格el-table怎样隐藏某一列el-table-column上添加v-if="false" 1234567891011121314151617181920212223<el-table v-loading="loading" :data="bczglList" @selection-change="handleSelectionChange"> <el-table-column label="id" align="center" prop="id" v-if="false" /> <el-table-column label="班次组编号" align="center" prop="bczbh" /> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['kqgl:bczgl:edit']" >修改</el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['kqgl:bczgl:remove']" >删除</el-button> </template> </el-table-column></el-table> 先记着Vue同一个dom元素绑定多个点击事件如何绑定 1<el-button @click="dialogVisible = false;clearTempData()">取 消</el-button> 模板获取值 123<template slot-scope="scope"> <el-tag type="warning" v-for="ids in scope.row.typeID.split(',')">{{getGameType(ids)}}</el-tag></template> Store 数据 1agentId: _this.$store.getters.name.agentId,
评论