MultiBallSquare 产品开发文档
一、产品简介
1.产品目的
本产品为MultiBallSquare —— 花旗ESG评估平台的网站,目标:让投资者快速、清晰、全面了解到亚洲ESG全景;并获取有建设性的ESG投资建议和相关支撑数据。
2.产品功能

  1. 为投资者提供亚洲esg图景(基本政策动态,集中的行业实时动态,符合亚洲实际情况的esg指标框架);
  2. 为投资者提供客观的esg数据库,便于其灵活考察关注的数据。
  3. 以行业为单位为投资者提供有效的esg评估方案和esg评分排名,并实时监控esg指标权重的更新。
  4. 提供评估公司详细的esg评估文档。
  5. 可拓展但未开发业务构思:实地数据调研&采集【第三方合作外包业务或者自身提供专业团队】、负面信息监控&处理、ESG个性化定制服务。
    二、开发流程
    1.开发流程
  6. 需求分析,撰写产品需求文档
  7. 技术选型
  8. 书写接口文档
  9. 数据库设计
  10. 导入jar包
  11. 编码开发
  12. 测试
  13. 运维
    2.技术选型
    产品分前后端分离开发+算法端计算指标权重。
    前端分为web和android端。
  14. 前端:HTML,CSS,JavaScript,Vue,ElementUI,Echart开发。【开发工具:Webstorm】
  15. 后端:Springboot+Mybatis。【开发工具:IDEA,Navicat,PowerDesigner,Postman】
  16. 算法:神经网络模型——多层感知机,调用MLP库函数。【开发工具:Pycharm】
  17. 服务器:
  18. 服务器:阿里云服务器ECS
  19. 服务器操作系统:CentOS 8.5 64位
  20. 公网IP:47.113.230.20
  21. 数据库:MySQL
    三、文档说明
  22. 本文档为实际开发过程产品细节设计文档,参考产品需求文档,撰写实际开发产品时所使用的的数据类型、函数逻辑、界面设计。
  23. 阅读撰写文档的人员包括:前端(web,app)、后端、算法开发人员。
    四、产品开发内容
    1.前端
    1.1.界面总体设计
    MultiBallSquare采用VUE+Element+Echarts进行前端开发,布局采用导航栏在左,winth=180px,标题栏在右上,信息展示页面在右下。导航栏分首页、ESG简介、ESG数据、ESG信息来源、ESG产品与咨询、个人中心和管理员登录7个模块。标题栏有登录,联系我们,帮助,全局搜索,个人信息几个标签。点击导航栏对应子模块,在信息页面展示相应内容。信息页面总体采用栅栏布局,元素定位方式主要采用相对定位margin,padding。

1.2.首页设计
首页信息展示页面共6部分:

<div name="div1" id="div1" ref="div1">
<div style="margin-top: 50px" name="div2" id="div2" ref="div2">
<div style="margin-top: 50px" name="div3" id="div3" ref="div3">
<div style="margin-top: 50px" name="div4" id="div4" ref="div4">
<div style="margin-top: 50px" name="div5" id="div5" ref="div5">
页面布局如下:

技术点:采用栅栏布局一行4栏4个div小卡片。定位方式采用相对定位margin,padding。
代码如下:

首页中子页面采用分页查询方式展示信息,采用get请求发送每页信息展示条目数和当前页面给后端,后端返回数据,前端渲染数据到页面。代码如下:
//请求分页查询数据
this.request.get(“/intr/policy/page”, {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
}
}).then(res => {
//后端返回给前端json对象,前端发送给后端对象,后端映射,可以json,parm,url
this.Data = res.records
this.total = res.total
});
},
1.3.ESG数据展示页
载入页面时,向后端发送请求,若用户未登陆,报“401”异常,发出警告,跳转登录页面。若用户已登陆,后端返回数据,通过Echarts绘制图形。前端渲染,展示数据信息。代码如下:
this.request.get(“/echarts/members”).then(res=>{
//当权限验证不通过时给出提示
if(res.code == ‘401’){
alert(“请登录后查看数据!”)
// router.push(“/login”)
}
// option.xAxis.data = res.data.x
option.series[0].data = res.data
//数据准备完毕后再set
myChart.setOption(option)
})
页面布局如下:

1.4.ESG评估流程页面
展示MBS ESG评估流程,最后提供MBS评估报考下载,点击下载可以查看MBS评估的宁德时代ESG报告。布局如下:

技术点:通过标签,实现选择文件并携带文件参数访问http://‘+serverIp+’:9091/file/upload’接口地址实现文件上传功能。代码如下:

上传文件

通过在子路由file页面中,打开文件的url地址实现文件下载。访问下载接口的url地址为:/file/url。代码如下
download(url) {
//file后接url
window.open(url)
},
1.5.注册登录页面
前端通过 标签获取登录注册页面的用户信息,发送用户信息到/user/login和/user/register接口。后端处理数据,返回信息。
login() {
//校验表单
this.KaTeX parse error: Expected '}', got 'EOF' at end of input: … this.router.push(“/home”)
} else {
//失败返回失败信息
this.$message.error(res.msg)
}
})
}
});
}

register() {//校验表单this.$refs['userForm'].validate((valid) => {if (valid) {  // 表单校验合法if(this.user.password != this.user.confirmpassword){this.$message.error("两次输入密码不一致")return false;}this.request.post("/user/register", this.user).then(res => {//判断返回状态码if (res.code === '200') {this.$message.success("注册成功")} else {//失败返回失败信息this.$message.error(res.msg)}})}});
}

1.6.个人信息修改页面
采用标签选择本地图片,发送用户信息和头像给后端,后端更改数据库信息。最后返回数据,前端渲染展示数据,更改信息修改页面的用户信息,并更新父级组件的用户信息。代码和页面布局如下:
save() {
this.request.post(“/user”, this.form).then(res => {
if (res.code === ‘200’) {
this. m e s s a g e . s u c c e s s ( " 保存成功 " ) / / 触发父级更新 U s e r 的方法 t h i s . message.success("保存成功") // 触发父级更新User的方法 this. message.success("保存成功")//触发父级更新User的方法this.emit(“refreshUser”)
// 更新浏览器存储的用户信息
this.getUser().then(res => {
res.token = JSON.parse(localStorage.getItem(“user”)).token
localStorage.setItem(“user”, JSON.stringify(res))
})
} else {
this.$message.error(“保存失败”)
}
})
},

1.7.搜索功能

技术点:前端输入不完整信息,发送数据给后端,后端进行模糊查询,返回数据给前端。代码如下:

2.Android
1.
2.后端【按分层说明数据变量、函数逻辑、接口设计以及异常处理】

1)数据库设计:

2)接口设计
以接口 /home/foreign_news 为例,HomeForeignNewsMapper 继承于BaseMapper,可以实现自动拼接sql语句,所有的mapper都不需要编写一些通用方法也就是不用编写sql语句。可以提高开发效率。
国外新闻请求接口(分页查询):

@RestController
@RequestMapping(“/home”)

@Autowired
private HomeForeignNewsMapper homeForeignNewsMapper;

public class HomeController {

@PostMapping(“/foreign_news”)
public Result listForeignNews(@RequestBody PageParams pageParams){
Page page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();

Page<HomeForeignNews> newsPage = homeForeignNewsMapper.selectPage(page, queryWrapper);
List<HomeForeignNews> records = newsPage.getRecords();Integer total = homeForeignNewsMapper.selectCount(queryWrapper);
return Result.success(new Records(total, records));

}
当接收到前端包含请求体为 {“page”:,“pageSize”:5} 的接口请求后,使用Mabatis-plus提供的接口创建一个Page,并进行分页查询;因为最后还要向前端返回一个总条数total,所以再进行一次count查询,最后对结果进行封装并返回前端。

3)JWT进行跨域登录

Controller层代码:
@RestController
@RequestMapping(“login”)
public class LoginController {

@Autowired
private LoginService loginService;@PostMapping
public Result login(@RequestBody LoginParam loginParam){return loginService.login(loginParam);
}

}

Service 层代码和逻辑:
1.检查参数是否合法
2.根据用户名和密码去 user 表中查询 是否存在
3.如果不存在,登录失败
4.如果存在,使用jwt,生成token 返回给前端
5.token放入redis中,redis token:user信息 设置过期时间
(登录认证时候 先认证token字符串是否合法,去redis认证是否存在)
@Override
public Result login(LoginParam loginParam) {

    String account = loginParam.getAccount();String password = loginParam.getPassword();if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){return Result.fail(ErrorCode.PARAMS_ERROR.getCode(), ErrorCode.PARAMS_ERROR.getMsg());}password = DigestUtils.md5Hex(password + slat);SysUser sysUser = sysUserService.findUser(account, password);if (sysUser == null){return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());}String token = JWTUtils.createToken(sysUser.getId());redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);return Result.success(token);

}
最后返回token给前端

4)邮箱注册
具体思路:
1.判断参数是否合法
2.判断账户是否存在,存在则返回:账号已经被注册
3.不存在,则注册用户
4.生成token
5.存入redis 并返回
6.注意加上事务,一旦中间的任何过程出现问题,注册的用户需要回滚

@Override
public Result register(LoginParam loginParam) {String account = loginParam.getAccount();String password = loginParam.getPassword();String nickname = loginParam.getNickname();String captcha = loginParam.getCaptcha();String email = loginParam.getEmail();if (StringUtils.isBlank(account)|| StringUtils.isBlank(password)|| StringUtils.isBlank(nickname)|| StringUtils.isBlank(captcha)|| StringUtils.isBlank(email)){return Result.fail(ErrorCode.PARAMS_ERROR.getCode(), ErrorCode.PARAMS_ERROR.getMsg());//参数错误}SysUser sysUser = sysUserService.findUserByAccount(account);if (sysUser != null){return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(), ErrorCode.ACCOUNT_EXIST.getMsg());//账号已存在}String captchaJson = redisTemplate.opsForValue().get("CAPTCHA_"+email);if (StringUtils.isBlank(captcha)){ //过期return Result.fail(ErrorCode.EMAIL_ERROR.getCode(), "验证码过期");}else if (!captchaJson.equals(captcha)){return Result.fail(ErrorCode.EMAIL_ERROR.getCode(), ErrorCode.EMAIL_ERROR.getMsg());}sysUser = new SysUser();sysUser.setNickname(nickname);sysUser.setAccount(account);sysUser.setPassword(DigestUtils.md5Hex(password + slat));sysUser.setCreateDate(System.currentTimeMillis());sysUser.setLastLogin(System.currentTimeMillis());sysUser.setEmail(email);this.sysUserService.save(sysUser);String token = JWTUtils.createToken(sysUser.getId());redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.MINUTES);return Result.success(token);
}

5)登录拦截(未登录用户不能访问资源)

1.需要判断 请求的接口路径是否为 HandlerMethod(controller方法)
2.判断 token 是否为空, 如果为空:未登录
3.如果 token 不为空,登录验证 loginService checkToken
4.如果 认证成功 放行
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

@Autowired
private LoginService loginService;@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)){//还可能是 ResourceHttpRequestHandler,用来处理静态资源请求的handler,默认去classpath下的static目录去查询return true;}String token = request.getHeader("Authorization");if (StringUtils.isBlank(token)){Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());response.setContentType("application/json;charset=utf-8");response.getWriter().print(JSON.toJSONString(result));return false;}SysUser sysUser = loginService.checkToken(token);if (sysUser == null){Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());response.setContentType("application/json;charset=utf-8");response.getWriter().print(JSON.toJSONString(result));return false;}//验证成功,放行//我希望在controller中 直接获取用户的信息 怎么获取?UserThreadLocal.put(sysUser);return true;
}@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//如果不删除 ThreadLocal中用完的信息,会有内存泄露的风险//四种:强引用(不会被回收)、软引用(发生gc且内存不足,才会被回收)、弱引用(gc时直接回收)、虚引用(差不多不存在的引用)UserThreadLocal.remove();
}

}
登陆前:

登录后(每次请求携带token)

6)文件上传下载
1.逻辑思路:上传文件时,若文件内容不一样 得到不同标识码,先根据文件唯一标识码获取唯一的md5,根据唯一md5在数据库中查询是否有相同的文件,如果没有, 则上传文件到磁盘,记录url地址,如果有,直接返回url地址。最后存储url地址到数据库中。下载文件时,通过url获取硬盘中文件,再通过 ServletOutputStream输出流下载到本地浏览器。
2.代码实现
上传:
public String upload(@RequestParam MultipartFile file) throws IOException {
String originalFilename = file.getOriginalFilename();
String type = FileUtil.extName(originalFilename);
long size = file.getSize();

    // 定义一个文件唯一的标识码String fileUUID = IdUtil.fastSimpleUUID() + StrUtil.DOT + type;File uploadFile = new File(fileUploadPath + fileUUID);// 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录File parentFile = uploadFile.getParentFile();if(!parentFile.exists()) {parentFile.mkdirs();}String url;// 获取文件的md5,磁盘只有一份String md5 = SecureUtil.md5(file.getInputStream());// 从数据库查询是否存在相同的记录Files dbFiles = getFileByMd5(md5);if (dbFiles != null) {url = dbFiles.getUrl();} else {// 上传文件到磁盘file.transferTo(uploadFile);// 数据库若不存在重复文件,则不删除刚才上传的文件//本地下载文件的接口地址url = "http://localhost:9091/file/" + fileUUID;//阿里云服务器下载文件的接口地址

// url = “http://47.113.230.20:9091/file/” + fileUUID;
}

    // 存储数据库Files saveFile = new Files();saveFile.setName(originalFilename);saveFile.setType(type);saveFile.setSize(size/1024); // 单位 kbsaveFile.setUrl(url);saveFile.setMd5(md5);fileMapper.insert(saveFile);return url;
}

下载
public void download(@PathVariable String fileUUID, HttpServletResponse response) throws IOException {
// 根据文件的唯一标识码获取文件
File uploadFile = new File(fileUploadPath + fileUUID);
System.out.println(uploadFile);
// 设置输出流的格式
ServletOutputStream os = response.getOutputStream();
response.addHeader(“Content-Disposition”, “attachment;filename=” + URLEncoder.encode(fileUUID, “UTF-8”));
response.setContentType(“application/octet-stream”);
// 读取文件的字节流
os.write(FileUtil.readBytes(uploadFile));
os.flush();
os.close();
}

3.算法

  1. 模型构建思路
  2. 项目提出的问题背景:
    在不同的行业内同样的指标数据对于行业而言重要程度是不同的,不能一概而论;而在同一个行业内,不同的指标对于行业的影响力也有所不同,有些是关键问题,有些又不是那么重要。但是如何能够实现提供不同的指标数据之后,得到准确合理的esg评分呢?换句话说我们需要得到每个指标的权重,来计算得到esg分数。
    我们想到了可以以行业为单位,设计出我们(数据分析师)认为足够重要的指标,获取这些数据之后,构建模型获取每个指标的权重。模型暂且构建为y = wx。【x指标数据,维度为指标个数,w表示每个指标的权重,y为评估分数】
  3. 项目目标:
    首先规定一系列指标x_header和反应企业经营效果的评估标准y;每个指标和标准分别给定数据,假定指标数据为X(x1,x2,……xn)【一共n个属性】,标准为y。构建关系y=Wx。企图求出权重W。最终可以实现给定一组指标数据x_test,可以直接得出最终的评分。
  4. 模型选取:
    想要训练得到得到恰当的权重值,可以使用多元线性回归或者感知机来计算,本项目选取MLP感知机来实现。因为感知机可以有多个隐藏层,当隐藏层有多个节点,隐藏层有多层的时候,其权重不具备特备深刻的含义【不能直观感受某些标签更加重要,而某些标签不重要】。因此我们设计隐藏层只有一层,对比隐藏层只有一个节点(权重只有1个)和20个节点(每个属性有20个权重)的情况。
  5. 计算结果处理过程:
    由于源数据量较小,分析工程量大,只完成19、20年数据分析。针对面对数据量小的难题分别做出以下尝试并进行结果分析:
  6. 利用源数据进行分析,标准化处理之后输入MLP模型,MLP模型:一层隐藏层:分别设置20个节点和1个节点。当设置20个节点由于一个属性会拥有20个权重,最终权重为20个权重的和。
  7. 设置5次不同的随机种子得出5轮权重,求出权重值的平均值。
  8. 对平均权值做一个偏移——使权重都变成正数。偏移1
  9. 求出权值对应权值总和所占比例。——》最终该标签对应的权重。
  10. 围绕源数据将数据进行随机化操作(波动范围在-1.5~1.5倍源数据),随机波动不具有一致性,分别构造出202个和20002(包含源数据),标准化处理之后,输入MLP模型,MLP模型:一层隐藏层:分别设置20个节点和1个节点。当设置20个节点由于一个属性会拥有20个权重,最终权重为20个权重的和。
  11. 设置5次不同的随机种子得出5轮权重,求出权重值的平均值。
  12. 真实的2条源数据作为测试集:利用平均权重*真实的2条源数据对比标签label得分,判断得出最相近的模型设置。
  13. 求出5个随机种子结果的平均权重、权重偏移、权重比例,将权重比例作为最后每个标签的权重。
  14. 每个指标的权重*源数据打分得出最终实际的评分。
  15. 编码准备
  16. 指标设计:选取新能源行业进行该行业指标设计,并且标注指标得分如何计算【根据什么数字,如何计算出结果】。
  17. 确定标签label:百分制:净利润(1/3)+基本每股收益(1/3)+总市值(1/3)。
  18. 获取数据:分析企业年报和社会责任报告获取数据,查询相关数据库。得到对应指标的评分。
  19. 数据标准化处理:为了模型模拟准确,应该将所有数据(指标评分)缩放到同一范围内:0-100。有些由于无法获得行业水平直接利用数据比例为得分的情况,最终使用最大最小放缩比例来来使所有数据被放缩到0-1之间。
  20. 编码
  21. 数据构造

for i in range(10000):
temp = numpy.random.rand(1, len(x_train[0])) + 0.5
for j in (range(temp.shape[1])):
temp[0][j] = temp[0][j] * x_train[0][j]
x_train = numpy.row_stack((x_train,temp))

for i in range(10000):
temp = numpy.random.rand(1, len(x_train[1])) + 0.5
for j in range(temp.shape[1]):
temp[0][j] = temp[0][j] * x_train[1][j]
x_train = numpy.row_stack((x_train,temp))

for i in range(10000):
temp = numpy.random.rand(1,1) + 0.5
a = y_train[0] * temp[0][0]
y_train.append(a)

for i in range(10000):
temp = numpy.random.rand(1,1) + 0.5
a = y_train[1]* temp[0][0]
y_train.append(a)
2. # 提取数据
with open(‘test2.csv’, mode=‘r’, newline=“”, encoding=“utf-8”) as csv_file:
reader = csv.reader(csv_file)
reader = list(reader)
reader.remove(reader[0])
x_train = reader

数据预处理

for item in x_train:
for j in range(len(item)):
item[j] = float(item[j])

x_train = numpy.array(x_train)
y_train = numpy.array(y_train)
y_train = y_train.astype(‘int’)

数据标准化处理

min_max_scaler = preprocessing.MinMaxScaler()
x_train = min_max_scaler.fit_transform(x_train)

搭建模型

clf = MLPClassifier(activation=‘relu’, alpha=1e-05, batch_size=‘auto’, beta_1=0.9,
beta_2=0.999, early_stopping=False, epsilon=1e-08,
hidden_layer_sizes=(20), learning_rate=‘constant’,
learning_rate_init=0.001, max_iter=3000, momentum=0.9,
nesterovs_momentum=True, power_t=0.5, random_state=1, shuffle=True,
solver=‘lbfgs’, tol=0.0001, validation_fraction=0.1, verbose=False,
warm_start=False)

训练数据

clf.fit(x_train, y_train)

提取权重

weight = clf.coefs_
result = []
array = weight[0]
r = []
for i in range(len(array)):
sum = 0
for item in array[i]:
sum += item
r.append(sum)

for i in range(len(array[0])):
temp = []
for j in range(len(array)):
sum += array[j][i]
temp.append(array[j][i])
result.append(temp)
result.append®

数据输出

with open(“result.csv”,mode=‘w’,newline=“”, encoding=“utf-8”) as csv_file:
writer = csv.writer(csv_file)
writer.writerows(result)

  1. 结果评估
  2. 首先对比2个源数据,202,20002个构造数据的权重测试得分,对比发现源数据2的趋势最匹配结果。分析:虽然构造数据量足够大,但是由于数据随机构造,其排布规律并不符合实际企业得分规律,也就是基本都是噪声数据,没有有效规律可学习,因此模型得到的数据并不合理。
  3. 接着分别对比20个结点和1个结点的情况,对比发现,20个结点趋势【得分值相近对比;二者得分数据差别趋势对比;5次随机种子权重波动是否明显差异】明显好于1个结点。
  4. 最终选择只有两个源数据,隐藏层有20个结点的权重结果进行更进一步的分析。
  5. 可行性分析
  6. 逻辑合理,有一定可行性。
  7. 数据量过小可能造成模型欠拟合,无法得出更加准确的模型。
  8. 评分合理性有待考量,部分指标为非强制计算型数据,多根据自身客观判断打分,可能存在片面性、不合理性。
  9. 难点分析
  10. 指标设计是否具有针对性。
  11. 持有行业平均水平的数据,才能给一家公司打相对合理的分数,但是整个行业的相关数据难以获取。
  12. 训练人工神经网络模型必须要大量的数据才能得出合理的模型——恰当的权重。但是关键问题在于有效数据量很小【有确定年份的准确披露数据几乎为0】。只能保证模型理论自洽,但是实操的准确性有待考量。
  13. 而且由于模型训练具有特征提取的目的,必须保证源数据规律一致性贴近实际、反应真实企业发展情况,才能学习其背后的变化规律,提取出准确完整的标签权重。

了解Web项目前后端分离开发流程,这一篇就够了相关推荐

  1. 【JAVA微服务架构项目前后端分离开发-MyMooc教育在线学习平台】

    类似于中国Mooc的在线学习平台 文章目录 类似于中国Mooc的在线学习平台 一.项目描述 二.后台管理员系统细讲 三.前台用户系统细讲 四.前端技术点总结 五.后端技术点总结 六.遇到问题及解决方法 ...

  2. web前后端分离开发部署模式

    web前后端分离开发部署模式 在开始讨论这个话题之前我们先来认识一下传统的开发模式. 一.传统开发模式 相信很多做过Web开发童鞋应该都会经历这样一种开发模式,利用后端语言提供的模版引擎编写HTML/ ...

  3. 安居客住房系统-基于Python-Django前后端分离开发(一)——初始化项目及ORM关系映射

    "安居客"住房系统-基于Python-Django前后端分离开发 作者:代昌松 项目详情代码请参加我的代码仓库:https://gitee.com/dcstempt_ping/iz ...

  4. 014-Axios Ajax:前后端分离概述,发送json类型的参数,前后端分离开发:在线接口文档,前端工程化、Element、nginx

    第一节 Ajax概述 1.概述 概念: Asynchronous JavaScript And XML,异步的JavaScript和XML. 作用: 数据交换:通过Ajax可以给服务器发送请求,并获取 ...

  5. 前后端分离开发模式下后端质量的保证 —— 单元测试

    概述 在今天, 前后端分离已经是首选的一个开发模式.这对于后端团队来说其实是一个好消息,减轻任务并且更专注.在测试方面,就更加依赖于单元测试对于API以及后端业务逻辑的较验.当然单元测试并非在前后端分 ...

  6. ultraedit 运行的是试用模式_单元测试 —— 前后端分离开发模式下后端质量的保证...

    概述 在今天, 前后端分离已经是首选的一个开发模式.这对于后端团队来说其实是一个好消息,减轻任务并且更专注.在测试方面,就更加依赖于单元测试对于API以及后端业务逻辑的较验.当然单元测试并非在前后端分 ...

  7. Nodejs搭建前后端分离开发模式下的微信网页项目

    原文链接:<Nodejs搭建前后端分离开发模式下的微信网页项目>- 陈帅华 本文涉及对前后端分离及微信网页项目中的前端如何在本地环境中开发与调试的思考. 主要问题 1.如何配置微信公众平台 ...

  8. 《Vue+Spring Boot前后端分离开发实战》专著累计发行上万册

                杰哥的学术专著<Vue+Spring Boot前后端分离开发实战>由清华大学出版社于2021年3月首次出版发行,虽受疫情影响但热度不减,受到业界读者的热捧,截至今日 ...

  9. 视频教程-SpringBoot+Security+Vue前后端分离开发权限管理系统-Java

    SpringBoot+Security+Vue前后端分离开发权限管理系统 10多年互联网一线实战经验,现就职于大型知名互联网企业,架构师, 有丰富实战经验和企业面试经验:曾就职于某上市培训机构数年,独 ...

最新文章

  1. R语言自定义多分类混淆矩阵可视化函数(mutlti class confusion matrix)、R语言多分类混淆矩阵可视化
  2. SAP固定资产的几个关键日期
  3. EntityFramework附加实体
  4. 路径搜索 – Dijkstra 算法 (MATLAB实现)
  5. Android开发7:简单的数据存储(使用SharedPreferences)和文件操作
  6. 任何一个正整数都可以用2的幂次方表示(C语言版)
  7. sdut 最少拦截系统
  8. 牛客OI周赛2-提高组
  9. MVC仓储执行存储过程报错“未提供该参数”
  10. 数据源Display方法
  11. MyEclipse设置字体大小
  12. 谷歌安装FeHelper插件
  13. Google Chromecast
  14. 2019TFE计算机科学排名,美国留学|2019TFE Times 硕士专业排名
  15. 湘潭大学Oracle期末复习题
  16. slice 和 splice的区别是什么?
  17. 小程序做电商的硬伤 “正规军”入驻 草根望尘莫及
  18. 最详细tron节点搭建同步教程
  19. 新闻集团下周将推iPad报纸 网站屏蔽搜索引擎
  20. 反汇编-objdump

热门文章

  1. Xcode与C++之游戏开发: 2D图形
  2. 【BZOJ3884】【降幂大法】上帝与集合的正确用法
  3. html中tbody的作用,Html标签中thead、tbody、tfoot的作用
  4. LOL手游 -安装教程 含 IOS 与 Android
  5. 机器学习算法的透明度是一把双刃剑,该如何应对?
  6. 阿里健康开始第二波;得物取消996,厂有直呼总包少了17%;跟阿里高p聊天,时代的眼泪...
  7. 与帅哥往事相好的日子
  8. 女孩取名精选:好听儒雅、厚德载物的女孩名字
  9. python 财务分析可视化方法_Python数据可视化的四种简易方法
  10. Mangos T1-T6大全