为了账号安全,请及时绑定邮箱和手机立即绑定
1.3 id

id 属性类似于 class,不同的是 id 是唯一标签,不能重复;<div id='test'></div>类似于class,id 也是用于操作 dom 的标记,例如:929以上 demo 通过点击按钮,基于操控对应 dom 的 class 来控制按钮的显示与隐藏。

2.12 name

networks下的name的作用类似于 container_name指令,在这个例子中,mynetwork 创建的 docker 网络被命名为 my_network, 使用命令 docker network ls 可以查看到它。

4. 查看用户的 ID 信息

若想要查看某一个 Linux 用户的 组ID(GID) 和 用户ID(UID) ,可以使用 id 命令,命令如下:id rootid user_name01id user_name02执行结果如下图:Tips:最后的字段 组=gid 表示的是用户所在组,这里既可以看到初始组,如果有附加组,则也能看到附加组。

4.1 name() 属性

定义:该属性就是描述接口中参数的名称。使用方法:在 ApiImplicitParam 注解中声明 name 的值即可。例如,对于用户接口,该接口中存在一个用户对象参数 user ,我们只需要将 name 的值写为 ‘user’ 就好了,即表明该接口中有一个名称为 user 的参数(现在你不需要理解业务代码代表什么意思,重点看接口类上使用的注解及属性即可,下同)。@ApiImplicitParam(name = "user")public ServerResponse<User> userLogin(User user){ // do something...}代码解释:第1行,我们在用户登录接口方法的上方,定义了 ApiImplicitParam 注解的 name 属性的值,来描述该方法中参数的名称。显示结果:可以看到,在使用红色框框起来的地方就是我们使用 name 属性描述的参数名称了。Tips :在实际开发工作中,name 属性的值通常被描述为接口方法中参数的名称,一般情况不用单独来描述。name 属性的使用不是必须的,即每个接口方法中不一定要使用 name 属性,name 属性的适用范围应该根据接口业务要求来定。

4.7 通过 JdbcTemplate 操作数据库

通过 JdbcTemplate 进行增删改查操作非常简洁, Spring 官方封装了原生 JDBC 中冗余的模板代码,使数据库访问操作更加简洁,代码如下:实例:/** * 商品数据库访问类 */@Repository // 标注数据访问类public class GoodsDao { @Autowired private JdbcTemplate jdbcTemplate; /** * 新增 */ public void insert(GoodsDo goods) { jdbcTemplate.update("insert into goods(name,price,pic)values(?,?,?)", goods.getName(), goods.getPrice(), goods.getPic()); } /** * 删除 */ public void delete(Long id) { jdbcTemplate.update("delete from goods where id =?", id); } /** * 更新 */ public void update(GoodsDo goods) { jdbcTemplate.update("update goods set name=?,price=?,pic=? where id=?", goods.getName(), goods.getPrice(), goods.getPic(), goods.getId()); } /** * 按id查询 */ public GoodsDo getById(Long id) { return jdbcTemplate.queryForObject("select * from goods where id=?", new RowMapper<GoodsDo>() { @Override public GoodsDo mapRow(ResultSet rs, int rowNum) throws SQLException { GoodsDo goods = new GoodsDo(); goods.setId(rs.getLong("id")); goods.setName(rs.getString("name")); goods.setPrice(rs.getString("price")); goods.setPic(rs.getString("pic")); return goods; } }, id); } /** * 查询商品列表 */ public List<GoodsDo> getList() { return jdbcTemplate.query("select * from goods", new RowMapper<GoodsDo>() { @Override public GoodsDo mapRow(ResultSet rs, int rowNum) throws SQLException { GoodsDo goods = new GoodsDo(); goods.setId(rs.getLong("id")); goods.setName(rs.getString("name")); goods.setPrice(rs.getString("price")); goods.setPic(rs.getString("pic")); return goods; } }); }}getById 和 getList 方法中使用了匿名内部类,如果不了解的可以先去学习下相关知识。

2.2 构造函数的使用

刚刚我们详细解释了 bean 标签内部的属性,经过几个小实例以后不禁也有个问题:如果我们定义的类中有一些初始化的参数,并且定义好了有参数的构造,通过 xml 配置文件如何体现呢?实现起来非常简单,跟我来进行一个小实例:改造 User 类:这是一个普通的 Java 类对象,包含两个属性及其 get 和 set 方法,并且提供了空参构造和有参构造,为了测试方便再覆写一个 toString 方法。public class User { private Integer id; private String name; public User() { } public User(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; }}xml 配置文件方式: <bean id="user" class="com.wyan.entity.User" > <constructor-arg name="id" value="1"></constructor-arg> <constructor-arg name="name" value="zs"></constructor-arg> </bean>测试结果:其实对于有参构造实例化对象而言,使用一个标签 constructor-arg 即可,表示构造的参数,如果有多个,可以继续添加,这里不多做演示。疑问导出:可能有同学会想,那么如果以后我们的属性需要动态更改呢?或者我们的属性不是基本类型而是另外的对象呢? 后续在第三章依赖注入多种属性的小节给大家讲解 。

2.2 操作数据库

执行数据库的一系列操作我们需要使用query实例方法。创建test_db数据库。client.query("create database if not exists test_db character set UTF8mb4 collate utf8mb4_bin;")创建一个 students 表,里面有一个姓名、年龄字段。client.query(%{ create table if not exists test_db.students( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255), age INT UNSIGNED, PRIMARY KEY ( id ) );})向students表中插入一条数据:小明,10岁。client.query("insert into test_db.students (name, age) values ('小明', 10);")将students表中小明的年龄更改为11岁。client.query("update test_db.students set age = 11 where name = '小明';")查看students的所有数据,需要对结果进行迭代。results = client.query("select * from test_db.students;")results.each do |result| p resultend# ---- 输出结果 ----{"id"=>1, "name"=>"小明", "age"=>11}删除name等于小明的这一条数据。client.query("delete from test_db.students where name = '小明';")

2.4 表单输出

Form 对象的另一个作用是将自身转为HTML。为此,我们只需简单地使用 print() 方法就可以看到 From 对象的 HTML 输出。(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.views import LoginForm>>> from django import forms>>> f = LoginForm()>>> print(f)<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" value="checked" class="checkbox" id="id_save_login" checked></td></tr>如果表单绑定了数据,则 HTML 输出包含数据的 HTML 文本,我们接着上面的 shell 继续执行:>>> login = LoginForm({'name': 'te', 'password': 'spyinx1111', 'save_login': False})>>> print(login)<tr><th><label for="id_name">账号:</label></th><td><ul class="errorlist"><li>账号名最短4位</li></ul><input type="text" name="name" value="te" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><ul class="errorlist"><li>密码需要包含大写、小写和数字</li></ul><input type="password" name="password" value="spyinx1111" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr> >>> login = LoginForm({'name': 'texxxxxxxx', 'password': 'SPYinx123456', 'save_login': False})>>> print(login)<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr>上面我们测试了两种情况,一种错误数据,一种是正常情况显示的 HTML。此默认输出的每个字段都有一个<tr>。需要注意以下几点:输出不会包括 <table> 和 </table> 以及 <form> 和 </form>,这些需要我们自己写到模板文件中去;每一个 Field 类型都会被翻译成一个固定的 HTML 语句,比如 CharField 表示的是 <input type="text">,EmailField 对应着 <input type="email"> , BooleanField 对应着 <input type="checkbox">;上面 HTML 代码里的 input 元素中的 name 属性值会从对应 Field 的属性值直接获取;每个字段的文本都会有 <label> 元素,同时会有默认的标签值 (可以在 Field 中用 label 参数覆盖默认值);另外,Form 类还提供了以下几个方法帮我们调整下 Form 的输出 HTML:Form.as_p():将表单翻译为一系列 <p> 标签;Form.as_ul():将表单翻译成一系列的 <li> 标签;Form.as_table():这个和前面 print() 的结果一样。实际上,当你调用 print() 时,内部实际上时调用 as_table() 方法。对上面的三个方法我们先进行实操演练,然后在看其源码,这样能更好的帮助我们理解这三个方法调用背后的逻辑。其操作过程和源码如下:>>> from hello_app.views import LoginForm>>> from django import forms>>> login = LoginForm({'name': 'texxxxxxxx', 'password': 'SPYinx123456', 'save_login': False})>>> login.as_p()'<p><label for="id_name">账号:</label> <input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></p>\n<p><label for="id_password">密码:</label> <input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></p>\n<p><label for="id_save_login">7天自动登录:</label> <input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></p>'>>> login.as_ul()'<li><label for="id_name">账号:</label> <input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></li>\n<li><label for="id_password">密码:</label> <input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></li>\n<li><label for="id_save_login">7天自动登录:</label> <input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></li>'>>> login.as_table()'<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr>\n<tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr>\n<tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr>'# 源码位置:django/forms/forms.py# ...@html_safeclass BaseForm: # ... def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Output HTML. Used by as_table(), as_ul(), as_p()." top_errors = self.non_field_errors() # Errors that should be displayed above all fields. output, hidden_fields = [], [] for name, field in self.fields.items(): html_class_attr = '' bf = self[name] bf_errors = self.error_class(bf.errors) if bf.is_hidden: if bf_errors: top_errors.extend( [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)} for e in bf_errors]) hidden_fields.append(str(bf)) else: # Create a 'class="..."' attribute if the row should have any # CSS classes applied. css_classes = bf.css_classes() if css_classes: html_class_attr = ' class="%s"' % css_classes if errors_on_separate_row and bf_errors: output.append(error_row % str(bf_errors)) if bf.label: label = conditional_escape(bf.label) label = bf.label_tag(label) or '' else: label = '' if field.help_text: help_text = help_text_html % field.help_text else: help_text = '' output.append(normal_row % { 'errors': bf_errors, 'label': label, 'field': bf, 'help_text': help_text, 'html_class_attr': html_class_attr, 'css_classes': css_classes, 'field_name': bf.html_name, }) if top_errors: output.insert(0, error_row % top_errors) if hidden_fields: # Insert any hidden fields in the last row. str_hidden = ''.join(hidden_fields) if output: last_row = output[-1] # Chop off the trailing row_ender (e.g. '</td></tr>') and # insert the hidden fields. if not last_row.endswith(row_ender): # This can happen in the as_p() case (and possibly others # that users write): if there are only top errors, we may # not be able to conscript the last row for our purposes, # so insert a new, empty row. last_row = (normal_row % { 'errors': '', 'label': '', 'field': '', 'help_text': '', 'html_class_attr': html_class_attr, 'css_classes': '', 'field_name': '', }) output.append(last_row) output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender else: # If there aren't any rows in the output, just append the # hidden fields. output.append(str_hidden) return mark_safe('\n'.join(output)) def as_table(self): "Return this form rendered as HTML <tr>s -- excluding the <table></table>." return self._html_output( normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', error_row='<tr><td colspan="2">%s</td></tr>', row_ender='</td></tr>', help_text_html='<br><span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_ul(self): "Return this form rendered as HTML <li>s -- excluding the <ul></ul>." return self._html_output( normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', error_row='<li>%s</li>', row_ender='</li>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_p(self): "Return this form rendered as HTML <p>s." return self._html_output( normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', error_row='%s', row_ender='</p>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=True, ) 可以看到,转换输出的三个方法都是调用 _html_output() 方法。这个方法总体上来看代码量不大,涉及的调用也稍微有点多,但是代码的逻辑并不复杂,是可以认真看下去的。给大家留个思考题:上面的 _html_output() 方法在哪一步将 field 转成对应的 html 元素的,比如我们前面提到的 CharField 将会被翻译成 <input type="text">这样的 HTML 标签。这个答案我将会在下一小节中给大家解答。

2.2 注册表单

<body> <h3><i class='fa fa-user-plus'></i> 注册</h3> <form action="/users/register" method="POST"> <div class="row"> {{ form.name.label }} {{ form.name() }} <b>{{ form.name.errors[0] }}</b> </div> <div class="row"> {{ form.password.label }} {{ form.password() }} <b>{{ form.password.errors[0] }}</b> </div> <div class="row"> {{ form.submit() }} </div> {{ form.hidden_tag() }} </form></body></html>form 是注册表单,包括 3 个字段:name、password、隐藏字段,根据 form 中字段 email 和 password 的属性,它被渲染为如下的 HTML 文件:<html><head> <meta charset='UTF-8'> <link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="/static/style.css" rel="stylesheet"> <title>登录</title></head><body> <div class="header"><i class='fa fa-sign-in'></i> 登录</div> <form action="/users/login" method="POST"> <div class="row"> <label for="name">姓名</label> <input id="name" name="name" required type="text" value=""> <b></b> </div> <div class="row"> <label for="password">密码</label> <input id="password" name="password" required type="password" value=""> <b></b> </div> <div class="row"> <input id="submit" name="submit" type="submit" value="登录"> </div> <input id="csrf_token" name="csrf_token" type="hidden" value="ImRlYTZjZDEwZjU3YjNjNGY0MDVkMDc4ZDhiZTMwNWM1OTk2MjhiMzAi.X2LvVA.0x7iz2PGVHH-r8dWf7KQNMkuSAE"> </form></body></html>这里注意两点:form.email.errors 和 form.password.errors 是一个错误信息列表,errors[0] 表示第一条错误信息;form.hidden_tag() 用于防范 CSRF 攻击,生成 <input id=“csrf_token”/> 标签,请参考相关词条。

4. 发送带有附件的邮件

让我们先创建一个 txt 文件作为附件。echo "这是一个测试的txt文件" > test.txt然后让我再次修改 headers,增加Content-type: multipart/mixed。注意事项:我们需要使用 pack(“m”) 将函数转化为 base64 格式的。filename = File.expand_path(File.dirname(__FILE__) + "/test.txt")encodedcontent = [File.read(filename)].pack("m")msg = <<MESSAGEFrom: Andrew <#{sender_email}>To: Testuser <#{receiver_email}>Subject: Test Upload File/miDate: #{Time.now.strftime("%a, %d %b %Y %H:%M:%S +0800")}Message-Id: <#{rand.to_s[3...8]}.message.@163.com>Content-Transfer-Encoding:8bitContent-Type: multipart/mixed; name=\"#{filename}\"Content-Transfer-Encoding:base64Content-Disposition: attachment; filename="#{filename}"#{encodedcontent}MESSAGEputs msg 改好即可。

3. 上一节的思考题解答

记得上一节留的那个思考题吗?我们来认真解答下这个代码。其实那个翻译 Field 为 HTML 的核心代码就只有一句:bf = self[name]。我们来详细分析这一行代码的背后,做了哪些事情。def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): # 遍历form中的所有field,生成对应的html文本 for name, field in self.fields.items(): # ... # 最核心的一句 bf = self[name] if bf.is_hidden: # ... hidden_fields.append(str(bf)) else: output.append(normal_row % { 'errors': bf_errors, 'label': label, 'field': bf, 'help_text': help_text, 'html_class_attr': html_class_attr, 'css_classes': css_classes, 'field_name': bf.html_name, }) # ... return mark_safe('\n'.join(output)) 看到 bf = self[name] 这一句,我们第一反应应该时找 Form 类中定义的 __getitem__() 这个魔法函数,可以看到它的源码如下:# 源码位置:django/forms/forms.py# ... @html_safeclass BaseForm: # ... def __getitem__(self, name): """Return a BoundField with the given name.""" try: field = self.fields[name] except KeyError: raise KeyError( "Key '%s' not found in '%s'. Choices are: %s." % ( name, self.__class__.__name__, ', '.join(sorted(f for f in self.fields)), ) ) if name not in self._bound_fields_cache: self._bound_fields_cache[name] = field.get_bound_field(self, name) return self._bound_fields_cache[name]从这里我们可以知道,bf = self[name] 的执行结果是由下面两条语句得到:# 得到对应fieldfield = self.fields[name]# 返回的结果field.get_bound_field(self, name)有了这两个语句,我们可以在 Django 的 shell 进行相关的测试了,具体操作如下:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django import forms>>> from hello_app.views import LoginForm>>> login = LoginForm({'name': 'test1234', 'password': 'SPYinx1234', 'save_login': False})>>> bf = login['name']>>> bf<django.forms.boundfield.BoundField object at 0x7fd7ad9232e0>>>> str(bf)'<input type="text" name="name" value="test1234" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name">'>>> bf = login['password']>>> str(bf)'<input type="password" name="password" value="SPYinx1234" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password">'# 测试后面两条语句>>> field = login.fields['name']>>> bf = field.get_bound_field(login, 'name')>>> print(bf)<input type="text" name="name" value="test1234" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name">最后想再继续追下去,弄清楚到底如何翻译成 HTML 代码的,就要继续学习 django/forms/boundfield.py 中的 BoundField 类了。这个就做为课后练习了。

4.1 name() 属性

定义:该属性就是描述接口中参数的名称。使用方法:在 ApiParam 注解中声明 name 的值即可。例如,对于用户接口而言,在本例中,需要传递的参数是一个 user 对象,所以我们需要将 name 的值写为 ‘user’就可以了,这样,我们就能很清楚的知道,这个接口方法中传递的参数是一个 user 对象了,如下代码段所示(现在你不需要理解业务代码代表什么意思,重点看接口方法上使用的注解及属性即可,下同)。@ApiParam(name = "user")public User login(User user){ // 用户登录业务逻辑}代码解释:第1行,我们在 login 接口方法的上方使用了 @ApiParam 注解的 name 属性来描述该接口中的参数名称。显示结果:可以看到,在 Parameters 内容区中用红框圈起来的 Parameter 参数的名称就是我们使用 name 属性来描述的接口参数名称。Tips :在实际开发工作中,name 属性的值一般都是根据接口方法中的形参来描述,即接口方法中默认声明的参数名称,除非有特殊说明才可以描述与形参名称不同的值。如果我们没有使用 name 属性来描述参数的名称,则参数名称默认为接口中自带的参数名称。

3.2 带过滤条件的监听

若提供了 query 参数,则 ECharts 在执行回调前,会先判断事件源是否满足过滤条件。 query 参数支持 string、object 两种形式,当使用字符串时,内容格式可以是 mainType、mainType.subType 两种形式,例如:1299示例有两个 click 回调,第一个指定过滤参数为 series,将在所有图表发生单击事件时执行回调;第二个指定过滤参数为 series.line,则只在折线图发生单击事件时触发。示例效果:query 还可以以对象方式传入,对象可以包含如下属性:{ <mainType>Index: number // 组件 index <mainType>Name: string // 组件 name <mainType>Id: string // 组件 id dataIndex: number // 数据项 index name: string // 数据项 name dataType: string // 数据项 type,如关系图中的 'node', 'edge' element: string // 自定义系列中的 el 的 name}其中 mainType 为组件类型,如 seriesIndex、xAxisIndex 等。示例:const option = { ... series: [ { data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'bar', name: 'series1' }, { data: [920, 1032, 1001, 1034, 1390, 1430, 1420], type: 'line', name: 'series2' }, ],};myChart.on( 'click', // 在 series1 上触发 {seriesName:'series1'}, function(e) { });字符串与对象形式过滤的功能不同,字符串形式只能根据组件类型、子类型过滤;对象形式则精确到组件、数据项维度。继续看看示例:1300示例声明过滤参数为 { dataIndex: 1 },则只会在数据项 1 上触发,效果:

4.2 Content-type

这一小节也可以当做附录来看,主要罗列一些常见的 Content-type 类型,给出一个参照,我们在了解之余,在实际使用 Ajax 的过程中,也可以随时回来翻看:Content-Type(Mime-Type)备注text/htmlHtml 文本格式类型text/xmlXml 文本格式类型text/plain纯文本格式类型image/jpegJpeg 图片格式类型image/pngPng 图片格式类型image/gifGif 图片格式类型application/jsonJson 数据格式类型application/xmlXml 数据格式类型application/xhtml+xmlHXTML 数据格式类型application/atom+xmlAtom Xml 聚合格式类型application/mswordWord 文档格式类型application/pdfPdf 文档格式类型application/octet-stream二进制数据格式类型application/x-www-form-urlencodedForm表单数据被编码成以 ‘&’ 分隔的键-值对,发送到服务端multipart/form-data表单文件上传使用的格式类型

3. 两个关键字的源码分析

来看它们的实现源码:type Readonly<T> = { readonly [K in keyof T]: T[K]}type Partial<T> = { [K in keyof T]?: T[K]}源码就使用了映射类型的语法 [K in Keys],来看这个语法的两个部分:类型变量 K:它会依次绑定到每个属性,对应每个属性名的类型。字符串字面量构成的联合类型的 Keys:它包含了要迭代的属性名的集合。我们可以使用 for...in 来理解,它可以遍历目标对象的属性。接下来继续分析:Keys,可以通过 keyof 关键字取得,假设传入的类型是泛型 T,得到 keyof T,即为字符串字面量构成的联合类型("name" | "age")。[K in keyof T],将属性名一一映射出来。T[K],得到属性值的类型。已知了这些信息,我们就得到了将一个对象所有属性变为可选属性的方法:[K in keyof T]?: T[K]进而可得:type Partial<T> = { [K in keyof T]?: T[K]}Readonly<T> 和 Partial<T> 都有着广泛的用途,因此它们与 Pick 一同被包含进了 TypeScript 的标准库里:type Pick<T, K extends keyof T> = { [P in K]: T[P]}interface User { id: number age: number name: string}type PickUser = Pick<User, 'id'>代码解释:最后一行,就相当于 type PickUser = { id: number }。

4. 模板文件 login.html

<html><meta charset='UTF-8'><h1>登录</h1><form class="form" method="POST"> <div> {{ form.email.label }} {{ form.email() }} <b>{{ form.email.errors[0] }}</b> </div> <div> {{ form.password.label }} {{ form.password() }} <b>{{ form.password.errors[0] }}</b> </div> <div> {{ form.submit() }} </div> {{ form.hidden_tag() }}</form></html>login.html 是用于描述登录界面的模板,根据 form 中字段 email 和 password 的属性,它被渲染为如下的 HTML 文件:<html><meta charset='UTF-8'><title>登录</title><h1>登录</h1><form class="form" method="POST"> <div> <label for="email">邮箱</label> <input id="email" name="email" required type="text" value="tom"> <b>请输入正确的邮箱</b> </div> <div> <label for="password">密码</label> <input id="password" name="password" required type="password" value=""> <b>密码至少包括 6 个字符</b> </div> <div> <input id="submit" name="submit" type="submit" value="登录"> </div> <input id="csrf_token" name="csrf_token" type="hidden" value="ImY2Y2NhMWNjZDRlYWE1ZDE2ODRiZDFlYzY5ZGNhNDIzZWJmNWQ0OTQi.X1Fo_w.13ad614Bw80RgPhc9RFdjZw7-q0"></form></html>这里注意两点:form.email.errors 和 form.password.errors 是一个错误信息列表,errors[0] 表示第一条错误信息;form.hidden_tag() 用于防范 CSRF 攻击,生成 <input id=“csrf_token”/> 标签,请参考相关词条。

3.2 #id 选择器

使用 #id 定义样式,比如 #column,就表示定义的是 id 为 column 标签的样式。实例:<style> #column { width: 50%; } </style>

2.4 @Resources 注解

此注解的作用是指定依赖按照 id 注入,还是按照类型注入。当只使用注解,但是不指定注入方式的时候,默认按照 id 注入,找不到时再按照类型注入。语法如下:@Resource //默认按照 id 为 userDao的bean实例注入 @Resource(name="userDao") //按照 id 为 userDao的bean实例注入 @Resource(type="UserDao") //按照 类型 为 UserDao的bean实例注入 这里就只做个语法的介绍,注解的使用大同小异,大家按照上方步骤自行测试即可。

3. Django 中对 SQL 注入漏洞做的工作

Django 内置的 ORM 模型某种程度上帮我们处理好了 SQL 注入问题,我们尽量使用 Django 内置 ORM 模型的 api 去对数据库中的表进行增删改查操作,它会根据我们所使用的数据库服务器的转换规则,自动转义特殊的SQL参数,从而避免出现 SQL 注入的问题。这个操作被运用到了整个 Django 的 ORM 模型的 api 中,但也有一些例外,如给 extra() 方法的 where 参数, 这个参数故意设计成可以接受原始的 SQL,并使用底层数据库API的查询。我们来看存在 SQL 注入漏洞和正确操作者两种写法:# 存在SQL注入漏洞代码name = 'Joe' # 如果first_name中有SQL特定字符就会出现漏洞User.objects.all().extra(where=["name='%s' and password='%s'" % (name, password)])# 正确方式User.objects.all().extra(where=["name='%s' and password='%s'"], params=[name, password])我们前面在 ORM 操作中建立了一个 user 表,对应的 model 类如下:# 代码位置: hello_app/models.pyclass User(models.Model): name = models.CharField('用户名', max_length=20) password = models.CharField('密码', max_length=50) email = models.EmailField('邮箱') def __str__(self): return "<%s>" % (self.name) class Meta: # 通过db_table自定义数据表名 db_table = 'user'这个表中有我们之前第16节中测试的11条数据,我们来拿这个表来完成相关 SQL 注入的实验。我们现在用两种方式来实现 SQL 注入:在 Django 中使用原生 SQL 操作 MySQL 数据库。下面是两种写法,分别对应着存在 SQL 注入漏洞和安全的操作:>>> from django.db import connection>>> cur = connection.cursor()# 存在注入漏洞,绕过了判断语句>>> cur.execute("select * from user where name='%s' and password='%s'" % ("' or 1=1 #", 'xxx'))11# 使用这种方式会避免上述问题>>> cur.execute("select * from user where name=%s and password=%s", ["' or 1=1#", 'xxx'])0>>> 在 Django 的 ORM 模型中使用 extra() 方法来构建 SQL 注入漏洞:>>> from hello_app.models import User# 实现SQL注入>>> User.objects.all().extra(where=["name='%s' and password='%s'" % ("') or 1=1 limit 1#", 'xx')])query=b"SELECT `user`.`id`, `user`.`name`, `user`.`password`, `user`.`email` FROM `user` WHERE (name='') or 1=1 limit 1#' and password='xx') LIMIT 21"<QuerySet [<User: <test>>]># 安全操作>>> User.objects.all().extra(where=["name=%s and password=%s"], params=["') or 1=1 limit 1#", 'xx'])query=b"SELECT `user`.`id`, `user`.`name`, `user`.`password`, `user`.`email` FROM `user` WHERE (name='\\') or 1=1 limit 1#' and password='xx') LIMIT 21"<QuerySet []># 正常取数据操作>>> User.objects.all().extra(where=["name=%s and password=%s"], params=["test", 'xxxxxx'])# 这个query是我为了方便在执行sql的地方加了个print语句,打印执行的sqlquery=b"SELECT `user`.`id`, `user`.`name`, `user`.`password`, `user`.`email` FROM `user` WHERE (name='test' and password='xxxxxx') LIMIT 21"<QuerySet [<User: <test>>]>注意:为什么这次注入的语句变成了"') or 1=1 limit 1#",这是因为我发现使用 extra() 方法是生成的 SQL 语句是这样的 (下面的 query 是我在源代码添加的一行 print 语句打印的):>>> User.objects.all().extra(where=["name=%s and password=%s"], params=["test", 'xxxxxx']) query=b"SELECT `user`.`id`, `user`.`name`, `user`.`password`, `user`.`email` FROM `user` WHERE (name='test' and password='xxxxxx') LIMIT 21" <QuerySet [<User: <test>>]>可以看到 extra 将 where 参数放到括号中,为了能注入正确的 SQL语句,就必须要添加 ) 去抵消 # 注释掉的原右括号,这样才能正常执行。到目前位置,我们在 Django 中对 SQL 注入漏洞进行了再现。为了避免 SQL 注入漏洞的方式也比较简单,主要遵循如下两个规则即可:尽量使用 Django 的 ORM 模型提供的方法去操作数据库;不要使用动态拼接 SQL 的方式,而是将 SQL 语句和参数分开放。

2.1 分布式 ID 的实现方式

本小节我们来简单介绍一下常用的分布式 ID 的实现方式,例如:UUID,Redis,雪花算法等。UUIDUniversally Unique Identifier 通用唯一标识符,由 32 个字符组成,采用 16 进制进行编码,定义了在时间和空间都完全唯一的系统信息。在 Java 中可以使用 java.util.UUID 的 randomUUID() 方法来获得:java.util.UUID.randomUUID().toString();UUID 可以在本地生成,生成速度快,不依赖网络和其它服务,但是 UUID 没有可以识别的特点,也没有顺序性。Redis 实现分布式 ID我们都知道 Redis 的性能非常高,而且还可以搭建集群。我们可以使用 Redis 的 Incr 命令来把 <key,value> 中 key 的数值加 1 并返回,如果这个 key 不存在,则 key 值会被初始化为 0,再执行 Incr 命令来进行加 1 操作。// 使用 incr(key) 来让 key 加 1long id = jedis.incr("id");使用 Redis 的方式生成分布式 ID 需要依赖 Redis 服务,还要保证 Redis 的高可用,否则 Redis 服务宕机会影响整个应用。雪花算法SnowFlake 雪花算法是 Twitter 公司推出的⼀个⽤于⽣成分布式 ID 的策略,基于这个算法可以生成 64 位 Long 型的 ID,它是由 1 位符号位,41 位的时间戳毫秒数,10 位的机器 ID,12 位的序列号这 4 种元素来组成的。理论上,雪花算法每秒可以生成 400 多万个 ID,完全可以支撑住分布式环境下高并发的场景。一些公司在雪花算法的基础上实现了自己的分布式 ID 的算法,比如:滴滴的 Tinyid,百度的 UidGenerator,美团的 Leaf 等。简单介绍了一些分布式 ID 的实现方式,接下来我们就使用 Zookeeper 来实现分布式 ID 。

2. 分布式 ID

在使用 Zookeeper 生成分布式的全局唯一 ID 之前,我们先来了解什么是分布式 ID,为什么要使用分布式 ID ,以及分布式 ID 的实现方式有哪些。分布式 ID,也就是在分布式的环境下,全局的唯一的 ID 。那么我们为什么要使用分布式 ID 呢?在单体结构的应用中,我们可以使用 MySQL 数据库的主键自增来为我们的数据设置唯一标识 ID,但是在分布式环境中,单个数据库的吞吐量成为整个应用的性能瓶颈,我们就可以搭建数据库集群来提升数据库的性能,此时如果还使用 MySQL 的主键自增来设置数据 ID 的话,就会出现重复的 ID,这样就会出现主键冲突的情况。如果使用分布式的全局唯一 ID 就不用担心会出现这个问题了。那么分布式 ID 的实现方式有哪些呢?接下来我们就对分布式 ID 的实现方式进行介绍。

3. 页面结构

<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>ajax example</title> </head> <style> table { border-collapse: collapse; text-align: center; width: 800px; } table td, table th { border: 1px solid #b8cfe9; color: #666; height: 30px; } table tr:nth-child(odd) { background: #fff; } table tr:nth-child(even) { background: rgb(246, 255, 255); } input { outline-style: none; border: 1px solid #ccc; border-radius: 3px; padding: 5px 10px; width: 200px; font-size: 14px; } button { border: 0; background-color: rgb(87, 177, 236); color: #fff; padding: 10px; border-radius: 5px; margin-top: 20px; } </style> <body> <div id="container"> <!--------列表查询模块-------------> <div class="query"> <h3>课程列表</h3> <table id="courseTable"></table> </div> <!--------列表查询模块 结束-------------> <!--------课程录入模块-------------> <div class="create"> <h3>添加课程</h3> <div> <label for="name">课程名称:</label><br /> <input type="text" id="name" name="name" /><br /> <label for="teacher">老师:</label><br /> <input type="text" id="teacher" name="teacher" /><br /> <label for="startTime">开始时间:</label><br /> <input type="date" id="startTime" name="startTime" /><br /> <label for="endTime">结束时间:</label><br /> <input type="date" id="endTime" name="endTime" /><br /> <button id="submitBtn">点击提交</button> </div> </div> <!--------课程录入模块 结束-------------> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script> <script src="/__build__/example.js"></script> </body></html>如上所示,我们首先定义好页面的结构和样式。可以清晰看出。页面主要分为两块,上面一块展示的是所有课程的结果,并且是表格呈现的,这里的 table 标签之所以没有嵌套,是因为我们会在后面 JavaScript 部分进行插入。下面一块则是录入课程的模块,分别有 课程名称、老师、开始时间和结束时间 4 个 input 标签。

1. label 标签的使用

label 需要和 input 标签搭配一起使用。LABEL 标签的 for 属性需要和 input 的 id 属性一致,这样才能点击 label 标签的内容使对应的 input 输入框自动获取焦点。代码如下:<label for="username">用户名</label><input type="text" placeholder="请输入内容" id='username'>效果如下:

5.1 例1. 根据 id 删除用户

请使用 MyBatis 完成对 imooc_user 表中通过 id 删除用户的功能。分析:按照 MyBatis 的开发模式,先在对应 UserMapper.xml 文件中添加根据 id 删除用户的 delete 标签,然后在 UserMapper.java 中增加上对应的方法即可。步骤:首先,在 UserMapper.xml 中添加 delete 标签,并在标签中写入 SQL :<delete id="deleteUserById"> DELETE FROM imooc_user WHERE id = #{id}</delete>然后在 UserMapper.java 中添加上对应的接口方法,方法接受 id 一个参数。package com.imooc.mybatis.mapper;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper { int deleteUserById(Integer id);}结果:通过如下代码,我们运行 deleteUserById 这个方法。UserMapper userMapper = session.getMapper(UserMapper.class);int rows = userMapper.deleteUserById(10);System.out.println(rows);// 一定要提交session.commit();session.close();成功后,id 为 10 的用户已被删除。

3.2 属性交换

Spring Security 支持 Open ID 的属性交换功能。例如,通过以下配置,我们可以获取到 Open ID 用户的邮箱和昵称属性:<openid-login><attribute-exchange> <openid-attribute name="email" type="https://axschema.org/contact/email" required="true"/> <openid-attribute name="name" type="https://axschema.org/namePerson"/></attribute-exchange></openid-login>本例中,每一个 Open ID 的属性类型值都是 URI 形式,定义在 https://axschema.org/ 中。required 属性代表该属性必须从认证中心返回,而属性的名称及定义规范则要和认证服务的定义保持一致。从认证中心返回的属性可以通过以下形式得到:OpenIDAuthenticationToken token = (OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();List<OpenIDAttribute> attributes = token.getAttributes();OpenIDAuthenticationToken 对象从 SecurityContextHolder 对象中获取。Google,Yahoo 和 MyOpenID 所提供的属性对象不尽相同。

9. Content-Type

Content-Type: text/html; charset=UTF-8告知客户端,响应内容的媒体类型,如 Json 报文/ Html 文件 / JavaScript 脚本 / 图片 / 视频 等。

1. form 表单的使用

form 标签和 ul select 标签类似,代表表单整体,而里面嵌套的元素则是表单具体的内容。我们来做一个用户名和密码的表单,这需要用到之前我们讲的 label 标签和 input 标签的知识,代码如下:<form> <label for="username">用户名</label> <input type="text" id='username'> <br> <label for="password">密码</label> <input type="password" id="password"></form>效果如下:表单呈现的形式和普通输入框无异,但它的作用就是我们要做提交表单的操作(既我们需要把用户输入的信息传给后台),那么普通的输入框就做不到这个功能了。那么提交表单的时候,我们可以给 form 标签加上一个 method 属性,这个属性表示当前提交表单的方式,一般为 get 或者 post,这个需要后台先行告知。form 标签还有一个 action 属性,表示表单提交的地址,这个也需要后台先行告知。

3.2 函数作用域

函数作用域就是在函数内部定义的变量,也就是局部作用域,在函数的外部是不能使用这个变量的,也就是对外是封闭的,从外层是无法直接访问函数内部的作用域的。function bar() { var name = 'imooc';}console.log(name); // undefined在函数内部定义的 name 变量,在函数外部是访问不了的。要想在函数外部访问函数内部的变量可以通过 return 的方式返回出来。function bar(value) { var name = ' imooc'; return value + name;}console.log(bar('hello')); // hello imooc借助 return 执行函数 bar 可以取到函数内部的变量 name 的值进行使用。

4. 方法的 name 属性

你有没有想过怎么获取对象上方法的名字?ES6 增加了函数的 name 属性,函数直接调用 name 会返回函数名。字面量对象上的方法也是函数,因此也有 name 属性。如下实例:var person = { name: "Jack", getName() { console.log(this.name); },};person.getName.name // "getName"上面代码中,getName() 方法的 name 属性返回函数名(即方法名)有两种特殊情况:Function 构造函数创造的函数,name 属性返回 “anonymous”;bind 方法创造的函数,name 属性返回 “bound” 加上原函数的名字。(new Function()).name // "anonymous"var doSomething = function() { // todo};doSomething.bind().name // "bound doSomething"如果对象的方法是一个 Symbol 值,那么 name 属性返回的是带中括号的 Symbol 的描述内容。const key1 = Symbol('description content');const key2 = Symbol();let obj = { [key1]() {}, [key2]() {},};obj[key1].name // "[description content]"obj[key2].name // ""

2. 应用场景

下面返回的数据可以看到创建时间字段 created_at 是时间戳,这种就需要转化成可读的日期格式,这个时候就可以用到 ThinkPHP 提供的数据模型的字段追加功能,它可以在原有字段的基础上追加出想要的其他字段数据。之前获取学生列表接口的时候返回数据如下:{ "total": 200, "per_page": 10, "current_page": 2, "last_page": 20, "data": [ { "id": 12, "name": "孙空", "age": 20, "id_number": "420117201005127996", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 1, "name": "赵四", "age": 24, "id_number": "420117201005124146", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 10, "name": "孙空", "age": 21, "id_number": "420117201005124671", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 9, "name": "钱学", "age": 19, "id_number": "420117201005121149", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 8, "name": "吴小明", "age": 21, "id_number": "420117201005123197", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 7, "name": "张红", "age": 19, "id_number": "420117201005123721", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 6, "name": "王五", "age": 25, "id_number": "420117201005123617", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 5, "name": "钱学", "age": 23, "id_number": "420117201005123085", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 4, "name": "赵四", "age": 20, "id_number": "420117201005128637", "created_at": 1603617951, "update_at": 0, "status": 1 }, { "id": 3, "name": "赵四", "age": 18, "id_number": "420117201005125711", "created_at": 1603617951, "update_at": 0, "status": 1 } ]}

首页上一页1234567下一页尾页
直播
查看课程详情
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号