【Flask】测试平台开发,邮件标记提测状态-第十六篇
根据提测ID,对数据表 request 和 apps 做联合表查询,返回需要详细信息,此接口用于前端跳转编辑提测页面的数据回填。resp_failed['message'] = '提测ID不能为空'try:# ✅ 使用正确的SQL,包含时间格式化sql = """R.title, \R.appId, \R.type, \R.scope, \R.wiki, \R.more, \"""# ✅ 使用参数
概述
实现提测单修改和邮件标记开发
提测详细查询接口
根据提测ID,对数据表 request 和 apps 做联合表查询,返回需要详细信息,此接口用于前端跳转编辑提测页面的数据回填。
@test_manager.route("/api/test/info", methods=['GET'])
def getTestInfo():
test_id = request.args.get('id')
resp_success = format.resp_format_success
resp_failed = format.resp_format_failed
if not test_id:
resp_failed['message'] = '提测ID不能为空'
return resp_failed
connection = pool.connection()
try:
with connection.cursor() as cursor:
# ✅ 使用正确的SQL,包含时间格式化
sql = """
SELECT R.id, \
R.title, \
R.appId, \
R.developer, \
R.tester, \
R.CcMail, \
R.version, \
R.type, \
R.scope, \
R.gitCode, \
R.wiki, \
R.more, \
R.status, \
R.createUser, \
R.updateUser, \
DATE_FORMAT(R.createDate, '%%Y-%%m-%%d %%H:%%i:%%s') as createDate, \
DATE_FORMAT(R.updateDate, '%%Y-%%m-%%d %%H:%%i:%%s') as updateDate, \
DATE_FORMAT(R.start_time, '%%Y-%%m-%%d %%H:%%i:%%s') as start_time, \
DATE_FORMAT(R.end_time, '%%Y-%%m-%%d %%H:%%i:%%s') as end_time, \
A.note as appName, \
A.appId as appIdName
FROM request as R
LEFT JOIN apps as A ON R.appId = A.id
WHERE R.isDel = 0 \
AND R.id = %s \
"""
# ✅ 使用参数化查询,避免SQL注入
cursor.execute(sql, (test_id,))
data = cursor.fetchone()
if data:
resp_success['data'] = data
# 添加调试信息
print(f"查询到的数据: {data}")
print(f"开始时间: {data.get('start_time')}")
print(f"结束时间: {data.get('end_time')}")
else:
resp_failed['message'] = '未找到对应的提测信息'
return resp_failed
return resp_success
except Exception as err:
print(f"查询提测信息错误: {str(err)}")
resp_failed['message'] = f'查询失败: {str(err)}'
return resp_failed
finally:
connection.close()
提测修改接口
提测更新接口,这里没有像之前一样放在一个方法里进行处理,主要原因有个特殊逻辑处理,将接口代码分开使得结构能够更清晰,这个特殊处理逻辑就需要对比下哪些是更改的内容,然后如果在勾选了发送的的选项下,能够将其标明下,这样才使得修改通知邮件有意义。这里逻辑上大致为:
-
更改数据前先查询暂存一个变量中A
-
进行数据库更新,同时有变量B
-
如果勾选了发邮件,发送内容字段值进行AB对比,不同则进行背景高亮或者前后标注处理,代码中采用的是标记 A.某内容 变更为:B.某内容
@test_manager.route("/api/test/update", methods=['POST'])
def updateReqeust():
# 获取传递的数据,并转换成JSON
body = request.get_data()
body = json.loads(body)
# 定义默认返回体
resp_success = format.resp_format_success
resp_failed = format.resp_format_failed
# 必填参数校验
required_fields = ['id', 'appId', 'tester', 'developer', 'title']
for field in required_fields:
if field not in body:
resp_failed['message'] = f'{field} 不能为空'
return resp_failed
# 验证type字段
if body.get('type') is None:
resp_failed['message'] = 'type 提测类型不能为空'
return resp_failed
try:
# 使用连接池链接数据库(放在try块内)
connection = pool.connection()
with connection:
# 首先查询原有数据
with connection.cursor() as cursor:
sql = """
SELECT R.id, \
R.title, \
R.appId, \
R.developer, \
R.tester, \
R.version, \
R.type, \
R.status, \
DATE_FORMAT(R.createDate, '%%Y-%%m-%%d %%H:%%i:%%s') as createDate, \
DATE_FORMAT(R.updateDate, '%%Y-%%m-%%d %%H:%%i:%%s') as updateDate, \
DATE_FORMAT(R.start_time, '%%Y-%%m-%%d %%H:%%i:%%s') as start_time, \
DATE_FORMAT(R.end_time, '%%Y-%%m-%%d %%H:%%i:%%s') as end_time, \
A.note as appName
FROM request as R
LEFT JOIN apps as A ON R.appId = A.id
WHERE R.isDel = 0 \
"""
sql = "SELECT A.appId as appId, A.note as appName, R.id,R.title,R.developer,R.tester,R.CcMail,R.version,R.type,R.scope,R.gitCode,R.wiki,R.more,R.start_time,R.end_time FROM request as R , apps as A where R.appId = A.id AND R.isDel=0 AND R.id=%s"
cursor.execute(sql, (body['id'],))
data = cursor.fetchall()
if len(data) == 1:
old_test_info = data[0]
else:
old_test_info = None
print('原有数据请求查询异常!')
# 调试:打印接收到的数据
print(f"接收到的请求体: {body}")
# 检查时间字段值是否正确(修复前端传递错误值的问题)
start_time_value = body.get("start_time")
end_time_value = body.get("end_time")
print(f"原始start_time: {start_time_value}, 类型: {type(start_time_value)}")
print(f"原始end_time: {end_time_value}, 类型: {type(end_time_value)}")
# 验证时间字段值 - 如果值是'Super Admin'或其他无效值,设置为None
def validate_time_value(value):
if not value:
return None
# 检查是否是明显错误的值
if value == 'Super Admin' or value == 'null' or value == 'undefined':
print(f"检测到无效时间值: {value},设置为None")
return None
# 检查是否是有效的时间格式
try:
# 如果是ISO格式
if 'T' in value and ('Z' in value or '+' in value):
date_part = value.split('T')[0]
time_part = value.split('T')[1]
# 验证日期部分
if len(date_part) == 10 and date_part.count('-') == 2:
year, month, day = date_part.split('-')
if len(year) == 4 and len(month) == 2 and len(day) == 2:
# 移除时区信息和毫秒
if '.' in time_part:
time_part = time_part.split('.')[0]
if 'Z' in time_part:
time_part = time_part.replace('Z', '')
if '+' in time_part:
time_part = time_part.split('+')[0]
# 验证时间部分
if len(time_part) == 8 and time_part.count(':') == 2:
return f"{date_part} {time_part}"
# 如果是MySQL格式
elif len(value) == 19 and value.count('-') == 2 and value.count(':') == 2:
return value
except Exception as e:
print(f"时间验证错误: {e}")
print(f"无效的时间格式: {value}")
return None
start_time = validate_time_value(start_time_value)
end_time = validate_time_value(end_time_value)
print(f"验证后start_time: {start_time}")
print(f"验证后end_time: {end_time}")
# 确保type是整数
try:
request_type = int(body.get("type"))
except (ValueError, TypeError):
request_type = 1 # 默认值
# 更新操作
with connection.cursor() as cursor:
# 构建更新SQL
sqlUpdate = """UPDATE request SET title=%s , appId=%s,developer=%s,tester=%s,CcMail=%s,version=%s,`type`=%s,scope=%s,gitCode=%s,wiki=%s,`more`=%s,updateUser=%s,updateDate=NOW()"""
# 参数列表
params = [
body.get("title", ""),
body.get("appId"),
body.get("developer", ""),
body.get('tester', ""),
body.get("CcMail", ""),
body.get("version", ""),
request_type, # 使用转换后的整数
body.get("scope", ""),
body.get("gitCode", ""),
body.get("wiki", ""),
body.get("more", ""),
body.get("updateUser", "")
]
# 动态添加时间字段
if start_time is not None:
sqlUpdate += ", start_time=%s"
params.append(start_time)
print("添加有效的start_time到SQL")
else:
sqlUpdate += ", start_time=NULL"
print("start_time设置为NULL")
if end_time is not None:
sqlUpdate += ", end_time=%s"
params.append(end_time)
print("添加有效的end_time到SQL")
else:
sqlUpdate += ", end_time=NULL"
print("end_time设置为NULL")
sqlUpdate += " WHERE id=%s"
params.append(body.get("id"))
print(f"最终SQL: {sqlUpdate}")
print(f"最终参数: {params}")
# 执行SQL
cursor.execute(sqlUpdate, tuple(params))
affected_rows = cursor.rowcount
connection.commit()
print(f"影响行数: {affected_rows}")
# 邮件发送逻辑
is_email = body.get('isEmail')
print(f"邮件发送判断: isEmail={is_email}, 类型: {type(is_email)}")
# 正确的判断逻辑
if is_email and str(is_email).lower() in ['true', '1', 'yes']:
print("开始发送邮件...")
# 类型转换
try:
request_type = int(body.get('type', 1))
except (ValueError, TypeError):
request_type = 1
if request_type == 1:
rquest_type = '功能测试'
elif request_type == 2:
rquest_type = '性能测试'
elif request_type == 3:
rquest_type = '安全测试'
else:
rquest_type = '未知类型'
# 构建收件人列表
receivers = []
if body.get("tester"):
receivers.extend([email.strip() for email in body["tester"].split(',') if email.strip()])
if body.get("developer"):
receivers.extend([email.strip() for email in body["developer"].split(',') if email.strip()])
if body.get("CcMail"):
receivers.extend([email.strip() for email in body["CcMail"].split(',') if email.strip()])
# 去重
receivers = list(set(receivers))
print(f"收件人列表: {receivers}")
subject = '【提测更新】' + body.get('title', '无标题')
contents = []
contents.append('<strong>[提测应用]</strong>')
# 获取应用名(需要从appId查询或使用body中的appName)
app_name = body.get('appName', '')
if not app_name and old_test_info:
app_name = old_test_info.get('appName', '')
if old_test_info and old_test_info.get('appName') != app_name:
contents.append(f"{old_test_info.get('appName', '')}变更为:{app_name}")
else:
contents.append(app_name)
contents.append('<strong>[提测人]</strong>')
if old_test_info and old_test_info.get('developer') != body.get('developer'):
contents.append(f"{old_test_info.get('developer', '')}变更为:{body.get('developer', '')}")
else:
contents.append(body.get('developer', ''))
contents.append('<strong>[提测版本]</strong>')
if old_test_info and old_test_info.get('version') != body.get('version'):
contents.append(f"{old_test_info.get('version', '')}变更为:{body.get('version', '')}")
else:
contents.append(body.get('version', ''))
contents.append('<strong>[测试内容]</strong>')
if old_test_info and old_test_info.get('scope') != body.get('scope'):
contents.append(f"{old_test_info.get('scope', '')}变更为:{body.get('scope', '')}")
else:
contents.append(body.get('scope', ''))
contents.append('<strong>[相关文档]</strong>')
if old_test_info and old_test_info.get('wiki') != body.get('wiki'):
contents.append(f"{old_test_info.get('wiki', '')}变更为:{body.get('wiki', '')}")
else:
contents.append(body.get('wiki', ''))
contents.append('<strong>[补充信息]</strong>')
if old_test_info and old_test_info.get('more') != body.get('more'):
contents.append(f"{old_test_info.get('more', '')}变更为:{body.get('more', '')}")
else:
contents.append(body.get('more', ''))
# 添加时间信息
if body.get('start_time'):
contents.append('<strong>[开始时间]</strong>')
contents.append(body.get('start_time', ''))
if body.get('end_time'):
contents.append('<strong>[结束时间]</strong>')
contents.append(body.get('end_time', ''))
print(f"邮件主题: {subject}")
print(f"邮件内容: {contents}")
try:
reuslt = sendEmail(receivers, subject, contents)
sendOk = 1 if reuslt else 2
print(f"邮件发送结果: {'成功' if reuslt else '失败'}")
except Exception as email_error:
print(f"邮件发送异常: {email_error}")
sendOk = 2
# 更新邮件发送状态
try:
with connection.cursor() as cursor:
updateEmail = "UPDATE request SET sendEmail=%s, updateUser=%s, updateDate=NOW() WHERE id=%s"
cursor.execute(updateEmail, (sendOk, body.get("updateUser"), body.get('id')))
connection.commit()
print(f"邮件状态更新: {sendOk}")
except Exception as update_error:
print(f"更新邮件状态失败: {update_error}")
else:
print('不发送邮件!原因: isEmail条件不满足')
print(f"isEmail值: {is_email}")
return resp_success
except Exception as err:
print(f"更新接口完整错误信息: {str(err)}")
import traceback
traceback.print_exc()
resp_failed['message'] = f'提测更新失败: {str(err)}'
return resp_failed
定义请求接口
首先先定义好后端的接口请求,只需接着上次分享中的test.js中添加两个请求
编辑带参跳转与获取
在提测列表页面中增加点击事件并实现方法,方法中这次采用URL带参数的方式,如果还不了解Vue $router 跳转的几种方式,请参考之前远程路由实现部分,跳转除了动作参数值赋予“UPDATE”,另外还需要额外给选择编辑数据的ID,用于跳转后的再查询,当然也可以通过param隐式的将一行数据传递过去,但这里不太建议。
对于commit页面不使用从上一页拿整行的数据,主要是考虑到这个页面有可能会做刷新浏览器操作,如果是隐式数据就会丢失,而URL中的参数不会,可以再次被取到,可以尝试下区别。这里需要对原有action和新参数id判断获取值,并对提测信息进行查询初始化。
提测详细回填处理
getTestInfo的代码逻辑是实现查询并将所需要的字段值绑定 requestForm,另外这段代码还需要做两个特殊的处理,需要特别注意下:
-
应用的ID远程搜索下拉框绑定需要代码触发下查询,用户Label - value的回填
-
延迟200~300秒在绑定appId,要不在跳转的页面的时候会有个小问题,就是先显示appId,再显示appName的一个过程,具体现象大家可以将setTimeout这个注释掉,直接this.requestForm.appId = data.appId 做个对比。
getTestInfo() {
apiTestInfo(this.testId).then(response => {
const data = response.data
this.requestForm.id = data.id
this.requestForm.title = data.title
this.requestForm.developer = data.developer
this.requestForm.tester = data.tester
this.requestForm.CcMail = data.CcMail
this.requestForm.version = data.version
this.requestForm.type = data.type
this.requestForm.scope = data.scope
this.requestForm.gitCode = data.gitCode
this.requestForm.wiki = data.wiki
this.requestForm.more = data.more
this.requestForm.appName = data.appName
this.requestForm.isEmail = false
this.remoteMethod(data.appName)
// this.requestForm.appId = data.appId
setTimeout(() => {
this.requestForm.appId = data.appId
}, 300)
})
}
页面支持修改改造
template部分为了支持修改功能的实现,需要如图三处的变更或者添加
-
根据跳转动作显示对应的标题
-
判断是更改的操作,显示ID信息,状态不可更改
-
增加修改按钮,用v-if判断动作,也可直接使用一个button,对文字描述做判断,例如
<el-button type="primary" @click="onSubmit">{{testAction=='ADD'?'立即添加':'修改提测'}}</el-button>
修改数据提交
提测编辑页面最后一个改造就是对数据的提交部分,同ADD逻辑,仅改了下请求API。
联调测试
代码编写完成还是要做个系统的测试来进行功能的验证
CASE-1: 新建一个提测,验证添加功能是否受新的修改功能影响;
CASE-2: 修改刚刚创建的提测,检查数据查询回填,尤其是是服务应用显示是否正常,修改部分字段值,提交修改;
CASE-3: 检查邮件是否都有正常收到,修改邮件是否按预期内容进行了标注。
至此我们就实现完成了,可以看到编辑后邮件中有编辑,xxx变更为,xxxx变更为,这种效果
更多推荐
所有评论(0)