我的第一个python web开发框架(40)——后台日志与异常处理
后台权限和底层框架的改造终于完成了,小白也终于可以放下紧悬着的心,可以轻松一下了。这不他为了感谢老菜,又找老菜聊了起来。
小白:多谢老大的帮忙,系统终于改造完成了,可以好好放松一下了。
老菜:呵呵,对于后台管理系统功能,你觉得已经完工了吗?没有什么遗漏的吗?
小白:啊......权限管理完成后不就完了吗?还有功能要弄的吗?
老菜:如果光从使用角度来说,也可能说完成了,但还有一些细节还需要处理的,比如说日志和异常。
小白:前面不是做过日志处理了,将所有的异常都自动写到日志中,方便开发人员分析查看,还能自动发送异常通知邮件,另外对于客户端提交的所有数据,在bottle勾子那里也做了处理,都写入到日志中了,还有什么要处理的?
老菜:对于日志来说可以分为两块:
一是管理员的操作日志,因为后台管理操作涉及到数据安全,管理员的所有操作都需要记录下来,以便发生问题时可以找到关系人,同时有些业务系统交给相关人员使用以后,BOSS却不知道他们到底有没有登录使用,每天在系统做什么;
二是系统的异常和关键数据的记录,这个属于系统底层的日志,将所有异常和与金钱相关的操作信息全部记录下来,有故障时开发人员可以根据日志快速定位,及时修复问题。这方面我们前面已经做一部分了,在前面底层很多地方都做了try...except...处理,这是很必要的,但你有没有发现,我们的代码在本地经常运行的好好的,而将代码更新上服务器后即经常爆500错误却不知道,想要排查异常时也很不方便,但查看uwsgi等多个系统日志才行,有些异常你查来查去都查不出来,非常浪费时间,你清楚这些异常主要是由什么引起的吗?有没有想过用什么方法也可以做到实时通过推送通知了解这些错误呢?当然对于异常的发生是很难避免的,但是我们可以通过一些手段,让这些异常发生后即时通过邮件或微信等方式,将异常详情通知我们,然后快速修复问题。如果你对系统非常熟悉的话,有可能用户还没反应过来,十几秒你就将故障修复了,做到人不知鬼不觉,哈哈。
小白:是啊,异常问题是我最大痛的事情,很多时候明明本地调试的好好的,一到服务就挂了,找到找去也找不出问题所在,浪费了大量的时间。那么我们要怎么来进行改造呢?
老菜:接下来你看我讲解就知道了,主要是对已有代码进行修改。
在前面的数据结构设计时,我们有一个管理员操作日志表,接下来的改造主要是对这个表进行相关的操作。
首先我们需要创建这个日志表的逻辑类,由于我们的ORM是用字典来进行增改操作的,所以需要先组合字段字典,然后再执行对应的方法,为了让操作简化,我们需要在日志表逻辑类中添加一个方法,通过传参的方式来进行日志的添加操作,这样就可以免去我们组合字典的操作了。
- #!/usr/bin/env python
- # coding=utf-8
- from logic import _logic_base
- from common.string_helper import string
- from config import db_config
- class ManagerOperationLogLogic(_logic_base.LogicBase):
- """管理员操作日志管理表逻辑类"""
- def __init__(self):
- # 表名称
- self.__table_name = 'manager_operation_log'
- # 初始化
- _logic_base.LogicBase.__init__(self, db_config.DB, db_config.IS_OUTPUT_SQL, self.__table_name)
- def add_operation_log(self, manager_id, manager_name, ip, remark):
- """记录用户登录日志"""
- # 组合要更新的字段内容
- fields = {'manager_id':manager_id, 'manager_name':string(manager_name), 'ip':string(ip), 'remark':string(remark)}
- # 新增记录
- self.add_model(fields)
从代码中可以看到,add_operation_log()方法,它其实就是将要更新到数据库的参数传进来,在方法里组合成字典,然后调用add_model()进行更新操作,调用时用下面代码就可以了
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '登陆成功')
完成这个操作日志逻辑类和日志添加方法以后,要改造登录接口就简单多了,只需要在出错(密码错误、禁用)和成功时进行调用,记录到数据表就可以了,具体看代码。
登录接口除了需要添加日志记录以外,还需要处理一个安全问题,我们没有对多次输出密码错误进行处理,如果有人想要登录系统写个密码劳举器,可能很容易后台就给人攻破了,所以我们需要对这个做一个限制,比如说同一ip在指定时间内只能出错多少次,每次出错时都记录一下出错次数,当出错次数超出限制时,则拒绝用户登录。具体自行查看代码,这里我就不再详细说明了。
- #!/usr/bin/env python
- # coding=utf-8
- from bottle import put
- from common import web_helper, encrypt_helper, security_helper
- from common.string_helper import string
- from logic import manager_logic, manager_operation_log_logic
- @put('/api/login/')
- def post_login():
- """用户登陆验证"""
- ##############################################################
- # 获取并验证客户端提交的参数
- ##############################################################
- username = web_helper.get_form('username', '帐号')
- password = web_helper.get_form('password', '密码')
- verify = web_helper.get_form('verify', '验证码')
- ip = web_helper.get_ip()
- ##############################################################
- # 从session中读取验证码信息
- ##############################################################
- s = web_helper.get_session()
- verify_code = s.get('verify_code')
- # 删除session中的验证码(验证码每提交一次就失效)
- if 'verify_code' in s:
- del s['verify_code']
- s.save()
- # 判断用户提交的验证码和存储在session中的验证码是否相同
- if verify.upper() != verify_code:
- return web_helper.return_msg(-1, '验证码错误')
- ##############################################################
- ### 判断用户登录失败次数,超出次做登录限制 ###
- # 获取管理员登录密码错误限制次数,0=无限制,x次/小时
- limit_login_count = 10
- # 获取操作出错限制值
- is_ok, msg, operation_times_key, error_count = security_helper.check_operation_times('login_error_count', limit_login_count, False)
- # 判断操作的出错次数是否已超出了限制
- if not is_ok:
- return web_helper.return_msg(-1, msg)
- ##############################################################
- ### 获取登录用户记录,并进行登录验证 ###
- ##############################################################
- # 初始化操作日志记录类
- _manager_operation_log_logic = manager_operation_log_logic.ManagerOperationLogLogic()
- # 初始化管理员逻辑类
- _manager_logic = manager_logic.ManagerLogic()
- # 从数据库中读取用户信息
- manager_result = _manager_logic.get_model_for_cache_of_where('login_name=' + string(username))
- # 判断用户记录是否存在
- if not manager_result:
- return web_helper.return_msg(-1, '账户不存在')
- # 获取管理员id
- manager_id = manager_result.get('id', 0)
- # 获取管理员姓名
- manager_name = manager_result.get('name', '')
- ##############################################################
- ### 验证用户登录密码与状态 ###
- ##############################################################
- # 对客户端提交上来的验证进行md5加密将转为大写(为了密码的保密性,这里进行双重md5加密,加密时从第一次加密后的密串中提取一段字符串出来进行再次加密,提取的串大家可以自由设定)
- # pwd = encrypt_helper.md5(encrypt_helper.md5(password)[1:30]).upper()
- # 对客户端提交上来的验证进行md5加密将转为大写(只加密一次)
- pwd = encrypt_helper.md5(password).upper()
- # 检查登录密码输入是否正确
- if pwd != manager_result.get('login_password').upper():
- # 记录出错次数
- security_helper.add_operation_times(operation_times_key)
- # 记录日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '【' + manager_name + '】输入的登录密码错误')
- return web_helper.return_msg(-1, '密码错误')
- # 检查该账号虽否禁用了
- if not manager_result.get('is_enabled'):
- # 记录出错次数
- security_helper.add_operation_times(operation_times_key)
- # 记录日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '【' + manager_name + '】账号已被禁用,不能登录系统')
- return web_helper.return_msg(-1, '账号已被禁用')
- # 登录成功,清除登录错误记录
- security_helper.del_operation_times(operation_times_key)
- ##############################################################
- ### 把用户信息保存到session中 ###
- ##############################################################
- manager_id = manager_result.get('id')
- s['id'] = manager_id
- s['login_name'] = username
- s['name'] = manager_result.get('name')
- s['positions_id'] = manager_result.get('positions_id')
- s.save()
- ##############################################################
- ### 更新用户信息到数据库 ###
- ##############################################################
- # 更新当前管理员最后登录时间、Ip与登录次数(字段说明,请看数据字典)
- fields = {
- 'last_login_time': 'now()',
- 'last_login_ip': string(ip),
- 'login_count': 'login_count+1',
- }
- # 写入数据库
- _manager_logic.edit_model(manager_id, fields)
- # 记录日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '【' + manager_name + '】登陆成功')
- return web_helper.return_msg(0, '登录成功')
security_helper.py代码
- #!/usr/bin/env python
- # coding=utf-8
- from common import cache_helper, convert_helper, encrypt_helper
- def check_operation_times(operation_name, limiting_frequency, ip, is_add=True):
- """
- 检查操作次数
- 参数:
- operation_name 操作名称
- limiting_frequency 限制次数
- is_add 是否累加
- 返回参数:
- True 不限制
- False 限制操作
- """
- if not operation_name or limiting_frequency is None:
- return False, '参数错误,错误码:-400-001,请与管理员联系', '', 0
- # 如果限制次数为0时,默认不限制操作
- if limiting_frequency <= 0:
- return True, '', '', 0
- ##############################################################
- ### 判断用户操作次数,超出次数限制执行 ###
- # 获取当前用户已记录操作次数
- operation_times_key = operation_name + '_' + encrypt_helper.md5(operation_name + ip)
- operation_times = convert_helper.to_int0(cache_helper.get(operation_times_key))
- # 如果系统限制了出错次数,且当前用户已超出限制,则返回错误
- if limiting_frequency and operation_times >= limiting_frequency:
- return False, '您在10分钟内连续操作次数达到' + str(limiting_frequency) + '次,已超出限制,请稍候再试', operation_times_key, operation_times
- if is_add:
- # 记录操作次数,默认在缓存中存储10分钟
- cache_helper.set(operation_times_key, operation_times + 1, 600)
- return True, '', operation_times_key, operation_times
- def add_operation_times(operation_times_key):
- """
- 累加操作次数
- 参数:
- operation_times_key 缓存key
- """
- # 获取当前用户已记录操作次数
- get_operation_times = convert_helper.to_int0(cache_helper.get(operation_times_key))
- # 记录获取次数
- cache_helper.set(operation_times_key, get_operation_times + 1, 600)
- def del_operation_times(operation_times_key):
- """
- 清除操作次数
- 参数:
- operation_times_key 缓存key
- """
- # 记录获取次数
- cache_helper.delete(operation_times_key)
- def check_login_power(id, k, t, sessionid):
- """
- 检查拨号小信接口,验证用户是否有权限访问
- :param id: 用户id
- :param k: 32位长度的密钥串
- :param t: 时间戳
- :param sessionid: 当前用户的密钥
- :return: False=验证失败,True=验证成功
- """
- if not sessionid:
- return False
- return encrypt_helper.md5(str(id) + sessionid + str(t) + sessionid + str(id)) == k
想要记录用户的每一个操作记录,有两种方法,一是在每个接口那里添加日志记录,这样可以更详细的编写自定义日志说明,不过这样做的话工作量会比较大,也容易在复制粘贴中出错;还有就是,每一个后台接口都会调用权限判断方法,我们也可以在这个方法中直接添加日志记录,缺点就是每个访问操作想要说明的很细致很难做到,这里我们通过各种判断与组合方式,来写入对应的接口日志访问记录,难免会出现记录重复或记录说明不正确的情况。
下面是后台权限检查方法(_common_logic.py)
- #!/usr/bin/env python
- # coding=utf-8
- from bottle import request
- from common import web_helper, string_helper
- from logic import menu_info_logic, positions_logic, manager_operation_log_logic
- def check_user_power():
- """检查当前用户是否有访问当前接口的权限"""
- # 读取session
- session = web_helper.get_session()
- # session不存在则表示登录失效了
- if not session:
- web_helper.return_raise(web_helper.return_msg(-404, "您的登录已失效,请重新登录"))
- # 获取当前页面原始路由
- rule = request.route.rule
- # 获取当前访问接口方式(get/post/put/delete)
- method = request.method.lower()
- # 获取当前访问的url地址
- url = string_helper.filter_str(request.url, '<|>|%|\'')
- # 初始化日志相关变量
- _manager_operation_log_logic = manager_operation_log_logic.ManagerOperationLogLogic()
- ip = web_helper.get_ip()
- manager_id = session.get('id')
- manager_name = session.get('name')
- # 设置访问日志信息
- if method == 'get':
- method_name = '访问'
- else:
- method_name = '进行'
- # 获取来路url
- http_referer = request.environ.get('HTTP_REFERER')
- if http_referer:
- # 提取页面url地址
- index = http_referer.find('?')
- if index == -1:
- web_name = http_referer[http_referer.find('/', 8) + 1:]
- else:
- web_name = http_referer[http_referer.find('/', 8) + 1: index]
- else:
- web_name = ''
- # 组合当前接口访问的缓存key值
- key = web_name + method + '(' + rule + ')'
- # 从菜单权限缓存中读取对应的菜单实体
- _menu_info_logic = menu_info_logic.MenuInfoLogic()
- model = _menu_info_logic.get_model_for_url(key)
- if not model:
- # 添加访问失败日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '用户访问[%s]接口地址时,检测没有操作权限' % (url))
- web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限1" + key))
- # 初始化菜单名称
- menu_name = model.get('name')
- if model.get('parent_id') > 0:
- # 读取父级菜单实体
- parent_model = _menu_info_logic.get_model_for_cache(model.get('parent_id'))
- if parent_model:
- menu_name = parent_model.get('name').replace('列表', '').replace('管理', '') + menu_name
- # 从session中获取当前用户登录时所存储的职位id
- positions = positions_logic.PositionsLogic()
- page_power = positions.get_page_power(session.get('positions_id'))
- # 从菜单实体中提取菜单id,与职位权限进行比较,判断当前用户是否拥有访问该接口的权限
- if page_power.find(',' + str(model.get('id', -1)) + ',') == -1:
- # 添加访问失败日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '用户%s[%s]操作检测没有权限' % (method_name, menu_name))
- web_helper.return_raise(web_helper.return_msg(-1, "您没有访问权限2"))
- if not (method == 'get' and model.get('name') in ('添加', '编辑')):
- # 添加访问日志
- _manager_operation_log_logic.add_operation_log(manager_id, manager_name, ip, '用户%s[%s]操作' % (method_name, menu_name))
这里记录的日志与菜单管理记录相关,如果菜单项的命名或树列表不规范,则记录的日志可能就会偏差比较大。当然如果你有强迫症追求完美的话,可自行对它进行改造。比如说在菜单管理中添加一个字段,用来编写日志说明的,访问这个页面时直接将说明更新到操作日志表中就可以了,简单方便。而如果对操作内容想要更细致的,也可以在日志表中添加一个字段,将客户端提交的参数全部写入到字段里记录,这样对用户的操作就会更清晰了,当然如果用户更新新闻或文章类内容时,字段值也会比较大。大家可以根据需要来进行对应改造。
下图为操作日志表记录内容
后台管理还需要做个日志查看的页面,接口代码很简单,具体直接看源码,这里也不详细说明了
对于异常处理,大家其实都知道使用try...except...进行捕捉,然后记录异常信息或作对应处理。
而在接口发生500错误时,由于程序在服务器端执行,服务器环境与本地的开发环境有所不同,就很难直观的判断是什么原因引起的,可能是少上传了某个调用文件,也可能是新引用的包没有安装,又或者是代码中写错了代码,也有可能是变量为空引起的异常,反正可能情况非常之多,当接口非常多时,这些异常通过很隐蔽,只有等到该接口被调用时才能发现,如果处理不好,开发人员可能会花费不少时间在这上面。
当然也有办法是,所有的接口代码都放在try...except...里面执行,这样发生500的情况会大大减少,但代码看起来层级多了也不美观。对于这种简单重复统一的代码,python有一个非常好用的工具,那就是装饰器,我们可以编写一个装饰器方法给接口使用,从而实现我们想要的目的。
装饰器实现的原理就是,通过在函数头部引用装饰器,从而使程序执行代码时,先执行装饰器里面的代码,然后再调用引用装饰器的函数,最后再返回装饰器执行剩下的代码。简单的理解就是,原有A函数和装饰器B函数,当A函数引用装饰器B函数以后,A函数其实就变成B函数中被调用的一个方法,即B函数在执行过程中会调用A函数,执行完成A函数后返回想要的结果再继续执行后面的代码
先上代码,我们在异常操作包中(except_helper.py),添加下面方法:
- def exception_handling(func):
- """接口异常处理装饰器"""
- def wrapper(*args, **kwargs):
- try:
- # 执行接口方法
- return func(*args, **kwargs)
- except Exception as e:
- # 捕捉异常,如果是中断无返回类型操作,则再执行一次
- if isinstance(e, HTTPResponse):
- func(*args, **kwargs)
- # 否则写入异常日志,并返回错误提示
- else:
- log_helper.error(str(e.args))
- return web_helper.return_msg(-1, "操作失败")
- return wrapper
func就是注入到装饰器方法中的其他方法,由于我们的装饰器是给接口使用,所以执行过程中直接返回结果(见第6行代码),由于我们的代码在执行过程,有时会调用raise来中断代码执行,这样的话接口方法是没有返回值的,如果使用return来调用方法就会出现异常,所以在第9到10行,会调用方法重新执行一次接口方法,所以在开发时要注意,只有对那些出错时需要马上中断的地方,才使用raise这样保证重复执行接口方法不会造成数据错误。
当接口方法执行出现异常要抛出500时,这个装饰器就会捕捉到,然后通过调用log_helper.error()方法,将异常写入日志,并发送异常通知邮件通知开发人员。对于异常通知,如果你注册了微信企业号,你可以编写对应的代码与企业号进行对接,让你和相关人员在微信上可以实时接收到异常推送消息,方便即时发现问题然后处理问题。
下面是调用方法:
- @get('/api/system/department/<id:int>/')
- @exception_handling
- def callback(id):
- """
- 获取指定记录
- """
- # 检查用户权限
- _common_logic.check_user_power()
- _department_logic = department_logic.DepartmentLogic()
- # 读取记录
- result = _department_logic.get_model_for_cache(id)
- if result:
- return web_helper.return_msg(0, '成功', result)
- else:
- return web_helper.return_msg(-1, "查询失败")
只需要在接口路由和接口方法之间,添加@exception_handling就可以实现接口500时,接收异常邮件推送了。非常方便好用。
版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
python开发QQ群:669058475(本群已满)、733466321(可以加2群) 作者博客:http://www.cnblogs.com/EmptyFS/
我的第一个python web开发框架(40)——后台日志与异常处理的更多相关文章
- 我的第一个python web开发框架(41)——总结
我的第一个python web开发框架系列博文从17年6.7月份开始写(存了近十章稿留到9月份才开始发布),到今天结束,一年多时间,想想真不容易啊. 整个过程断断续续,中间有段时间由于工作繁忙停了好长 ...
- 我的第一个python web开发框架(14)——后台管理系统登录功能
接下来正式进入网站的功能开发.要完成后台管理系统登录功能,通过查看登录页面,我们可以了解到,我们需要编写验证码图片获取接口和登录处理接口,然后在登录页面的HTML上编写AJAX. 在进行接口开发之前, ...
- 我的第一个python web开发框架(1)——前言
由于之前经验不是很丰富,写的C#系统太过复杂,所以一直想重写,但学的越多越觉得自己懂的越少,越觉的底气不足.所以一直不敢动手,在内心深处对自己讲,要静下心来认真学习,继续沉淀沉淀.这两年多以来找各种机 ...
- 我的第一个python web开发框架(3)——怎么开始?
小白与小美公司经过几次接触商谈,好不容易将外包签订了下来,准备开始大干一场.不过小白由于没有太多的项目经验,学过python懂得python的基本语法,在公司跟着大家做过简单功能,另外还会一些HTML ...
- 我的第一个python web开发框架(22)——一个安全小事故
在周末的一个早上,小白还在做着美梦,就收到了小美的连环追魂call,电话一直响个不停. 小白打着哈欠拿起电话:早上好美女. 小美:出事了出事了,我们公司网站一早访问是一片空白,什么内容都没有了,你赶急 ...
- 我的第一个python web开发框架(2)——一个简单的小外包
第一部分说明 第一部分大概有20来章,主要讲的是一些开发常识.开发前中后期准备内容.开发环境与服务器部署环境安装设置.python基础框架结构与功能等内容,代码会比较简单. 本系列会以故事的方式,向大 ...
- 我的第一个python web开发框架(6)——第一个Hello World
小白中午听完老菜讲的那些话后一直在思考,可想来想去还是一头雾水,晕晕呼呼的一知半解,到最后还是想不明白,心想:老大讲的太高深了,只能听懂一半半,看来只能先记下来,将明白的先做,不明白的等以后遇到再学. ...
- 我的第一个python web开发框架(7)——本地部署前端访问服务器
PS:本系列内容进度节奏会放的很慢,每次知识点都尽量少一点,这样大家接触的知识点少了,会更容易理解,因为少即是多.另外,对于后面代码部分,虽然尽量不用那些复杂的封装和类,但它并不表示看了就能全部记住, ...
- 我的第一个python web开发框架(10)——工具函数包说明(一)
PS:原先是想直接进入功能开发,要用到什么函数时再创建,这样也容易熟悉每个函数的由来和使用方法,但考虑到这样操作,到时会经常在不同文件间切换,不好描述,容易造成混乱,所以还是使用函数库这种方式来说明. ...
随机推荐
- oracle创建表空间自增长和创建用户
/* 步骤: 1.创建表空间 2.创建用户 3.用户授权 */ /*创建表空间*/ create tablespace QCJ_TABLESPACE /*表空间物理文件名称*/ datafile 'Q ...
- E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?
使用sudo apt-get install nginx 时提示错误: 问题描述: E: 无法获得锁 /: 资源暂时不可用) E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占 ...
- netdom join 错误:指定的域不存在,或无法联系。
环境: 域控制器:feiquan.com IP:192.168.1.132 客户端:\\win-quan IP:192.168.1.129(动态) 域控制器可以和客户端ping通,但客户端加入域时就 ...
- Windows2008安装组件命令行工具ServerManagerCmd用法介绍
转自:http://blog.sina.com.cn/s/blog_537de4b5010128al.html Windows2008 安装组件服务等内容比原来复杂的多,用鼠标点来点去,既繁琐也缓慢, ...
- 第16次CCF CSP认证-第5题-317 号子任务(subtask317)-图论最短路径
[题目背景]“你在平原上走着走着,突然迎面遇到一堵墙,这墙向上无限高,向下无限深,向左无限远,向右无限远,这墙是什么?”——<流浪地球>原著我们带着地球去流浪了,为了处理流浪过程中可能会发 ...
- Docker 容器与宿主机网段冲突导致网络无法 ping 通的解决方案
docker 容器网络默认使用 bridge 桥接模式,正常情况下,容器会使用 daemon.json 中定义的虚拟网桥来与宿主机进行通讯. 最近更新 Docker for mac 之后,发现以前容器 ...
- 流行的报表生成工具-JXLS
如果你还在为灵活的生成各种复杂报表犯愁,在为常用报表工具消耗大量内存担心.我推荐一个很好用的开源的Java报表生成工具. 本工具封装了强大的POI.但与POI不同的是,它可以用很简洁的代码生成复查的, ...
- .NET 下 模拟数组越界
前面一篇文章提到过 数组越界行为,虽然编译器为我们做了大量的检查工作让我们避免这些错误. 但是我觉得还是有必要模拟一下数组越界,感受一下这个错误. 那么对于.NET来说我们怎么来模拟数组越界呢? 一. ...
- C语言中的神兽strdup
C语言的确博大精深,在C语言的世界中遨游了那么多年,发现自己仍是菜鸟一枚,很多利器没有能够驾驭,今天介绍一个神兽,威力无比,但是却很少人能用得好. 函数原型: #include <string. ...
- 机器学习之十一问支持向量机(SVM)
推导了支持向量机的数学公式后,还需要对比和总结才能更深入地理解这个模型,所以整理了十一个关于支持向量机的问题. 第一问:支持向量机和感知机(Perceptron)的联系? 1.相同点: 都是一种属于监 ...