17.Web

一.WSGL
1.1.WSGL接口的实现
用Python 开发web服务端需要用到WSGL接口,WSGL接口定义很简单,只要求web开发者实现一个函数,就可以响应HTTP请求了。
一个最简单的web项目:

#t17_1.py
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return b'<h1>sss</h1>'

上面的application函数就是符合WSGL标准的一个HTTP处理函数,我的理解是,这个函数就相当于Java Web中的Servlet的doGet()或doPost()函数,接收的两个参数含义:
environ:一个包含所有HTTP请求信息的dict
start_response:一个发送HTTP响应的函数
函数中调用start_response()就发送了HTTP响应的header,header只能发送一次,所以这个方法也就只能调用一次,接收的两个参数一个是HTTP响应码,另一个是一组list表示的HTTP header,list的每个item是一个包含两个str的tuple,可以理解为key value形式传header。
最后,函数的返回值

b'<h1>sss</h1>'

作为HTTP响应的body发送给客户端。

有了WSGL,我们只需要考虑从environ里拿到HTTP请求信息,然后构造返回信息,通过start_response()发送header,最后返回构造的body。

注意,这个application和doPost()一样,是由服务器来调用的,并不是我们自己调用 的,environ和start_response两个参数我们也不能自己提供,返回的bytes也是由服务器来发送给客户端的,所以我们需要一个WSGL服务器,让这个web应用程序运行起来,测试时可使用Python内置的服务器,需要用到wsgiref模块(用纯Python实现的一个WSGL服务器的参考实现,不考虑性能,仅供开发测试使用)。

1.2运行WSGL服务
上面已经定义了web的处理函数application,现在需要启动WSGL服务器并把这个处理函数设置上,来了请求就由这个函数来处理:

#server.py
from wsgiref.simple_server import make_server

from test17.t17_1 import application

# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
http = make_server('', 8000, application)
print('Serving HTTP on port 8000')
# 开始监听HTTP请求
http.serve_forever()

再在浏览器访问(因为IP设置的是空,写本机地址):

在这里插入图片描述

获取url里的PATH_INFO:

#t17_2.py
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 如果端口后面有值就取([1:]是字符串切分,从第1位开始到结尾,目的是去年第一个斜线),否则返回'web',
    body = '<h1>sss , %s' % (environ['PATH_INFO'][1:] or 'web')
    return [body.encode('utf-8')]

在这里插入图片描述

二、使用Web框架
1.1使用web框架的意义
上面使用wsgl例子中,通过一个wsgl处理函数来处理所有的请求,根据每个http请求进行响应,注意是用一个处理函数处理所有的请求,我们需要先在接收到请求后在environ里取取出http请求信息判断这个请求是做什么的,再来决定做出什么响应,例如接收到用户登录请求,就得先去判断得知是请求登录的,而不是用户注册的,那么就得去数据库查询看用户是否存在,比对密码是否正确blablabla,如果是用户注册请求,就得去做注册相应的处理,并且有些接口的请求方式还要分POST、GET还是PUT等,不同的请求方式相应的处理也可能会不同,这样一来,所有的接口业务都在这一个地方处理,不说写起来容易出错,后期也无法维护。

1.2使用flask框架

from flask import Flask, request

app = Flask(__name__)


# 主页面
@app.route('/', methods=['GET', 'POST'])
def home():
    return '<h1>Home</h1>'


# 登录页面
@app.route('/signIn', methods=['GET'])
def signIn_page():
    return '''<form action="/signIn" method="post">
              <p><input name="username"></p>
              <p><input name="password" type="password"></p>
              <p><button type="submit">Sign In</button></p>
              </form>'''


# 登录接口请求
@app.route('/signIn', methods=['POST'])
def signin():
    if request.form['username'] == 'admin' and request.form['password'] == '123':
        return '<h3>Hello admin</h3>'
    return '<h3>Bad username or password</h3>'


if __name__ == '__main__':
    app.run()

===
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

然后在浏览器访问 http://127.0.0.1:5000/,就会由home()方法接收处理:

在这里插入图片描述

请求 http://127.0.0.1:5000/signIn,因为在网页直接输入网址访问是GET请求,就会由signIn_page()方法接收处理:

在这里插入图片描述
输入帐号密码后登录,因为form指定以POST方式请求,所以由signIn()方法处理:

在这里插入图片描述

如果输入一个错误的帐号或密码,会显示代码中写的错误消息:

在这里插入图片描述

有了web框架,就只需要专心处理url对应的处理函数了,上面这几个例子就类似于Java里Spring的映射接口。

三、模板
模板我理解为类似于Java里的jsp,可以嵌入html里配合后台数据更方便生成html;

from flask import Flask, request, render_template

app = Flask(__name__)


# 主页面
@app.route('/', methods=['GET', 'POST'])
def home():
    return render_template('home.html')  # 直接返回home.html页面


# 登录页面
@app.route('/signIn', methods=['GET'])
def signIn_page():
    return render_template('signForm.html')  # 直接返回signForm.html页面


# 登录接口请求
@app.route('/signIn', methods=['POST'])
def signin():
    username_ = request.form['username']
    password_ = request.form['password']
    if username_ == 'admin' and password_ == '123':
        return render_template('sign_ok.html', username=username_)  # 返回sign_ok.html页面并带一个username参数
    return render_template('signForm.html', username=username_,
                           message='Bad Username or password')  # 返回signForm.html页面并带一个username参数和message参数


if __name__ == '__main__':
    app.run()

---
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

flask用render_template加载网页,默认支持的模板是jinja2:

pip install jinja2

下面是html文件:
home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 style="font-style:italic">Home</h1>
</body>
</html>

sign_ok.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sign ok</title>
</head>
<body>
<p>Welcome, {{ username }}!</p>
</body>
</html>

signForm.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sign</title>
</head>
<body>
{% if message %}
<p style="color:red">{{message}}</p>
{% endif %}
<form action="/signIn" method=post>
    <legend>Please sign in:</legend>
    <p><input name="username" placeholder="Username" value="{{ username }}"></p>
    <p><input name="password" placeholder="Password" type="password"></p>
    <p>
        <button type="submit">Sign In</button>
    </p>
</form>
</body>
</html>

登录失败的模板复用了signFrom.html文件,通过一行文字来显示错误信息,注意要把模板文件放到templates文件夹下,.py文件和templates文件夹要放到同一级目录下;

在这里插入图片描述

操作结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

你写的我看不懂,最后 return findForNodeList1CODE; 这个值是下面这样的findForNodeList1CODE~~~~~~~[com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@798d632f, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@363996a0, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@396bc5cb, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@40657868, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@5263ef50, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@2895ff2a, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@4fb7226e, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@24fab5c8, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@69aed768, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@6e4509d8, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@1817f22c, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@69324013, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@76b8dfca, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@30f5130c, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@175401ac, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@6c1de98d, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@5ceb2d4b, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@441e28a9, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@73f018e7, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@7022d695, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@75262caf, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@40c809c2, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@338c161c, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@557cf886, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@11fbe5d2, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@117d6836, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@4212b0f3, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@7018d8ff, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@3e83765b, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@44c59565, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@13072026, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@256edd99, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@7a56c1, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@687c9516, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@42509e2f, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@110111e9, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@439f8156, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@4f477816, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@9a03637, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@1037e0b3, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@2f1d46d2, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@5bee1cef, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@53f7982b, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@7c6ac89e, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@39043a17, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@12a650bc, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@7dffb403, com.sinosoft.bi.web.dCompanyNode.entity.DCompanyNode@57fbbd1a] 控制层取值: // 获取 股东业务标识 查询条件列表 model.addAttribute("isshareholders", objectMapper.writeValueAsString(rFCUnderLAService.findforisshareholder(params)));
最新发布
07-10
<think>我们正在处理一个返回`List<DCompanyNode>`的Java方法,并需要理解这个数据结构以及如何在Spring MVC控制层使用它。 根据用户提供的代码片段,`DCompanyNode`似乎是一个自定义的类,用于表示树形结构的节点。它包含以下属性或方法: - `id`: 节点ID - `parent`: 父节点ID(可能为null,表示根节点) - `checked`: 布尔值,表示节点是否被选中(在代码中通过`setChecked(true)`设置) - `state`: 节点的状态(例如"open"表示展开) - `children`: 子节点列表(通过`addDCompanyNode`方法添加子节点,并通过`getChildren`获取) 方法`findforsurveyind`的逻辑: 1. 首先通过两个DAO方法查询出两组节点列表:`findForNodeList2CODE`和`findForNodeList1CODE`。 2. 根据参数`params`中的`surveyind`(一个逗号分隔的字符串)得到选中的公司ID数组。 3. 遍历选中的公司ID,在`findForNodeList2CODE`中找到对应的节点并设置`checked=true`。 4. 然后,遍历`findForNodeList1CODE`(这些节点可能是父节点),将`findForNodeList2CODE`中的节点作为子节点添加到对应的父节点下(通过比较子节点的`parent`和父节点的`id`)。 5. 设置父节点的状态:如果父节点没有子节点,则设置其状态为"open"(展开);否则,在添加子节点时,子节点的状态被设置为"open"(注意:这里设置的是子节点的状态,父节点的状态在循环结束后根据是否有子节点设置)。 最终返回的是`findForNodeList1CODE`,它包含了顶级节点(根节点),并且每个顶级节点已经包含了它们的子节点(树形结构)。 在Spring MVC控制层中,我们可以这样使用: 1. 在控制层方法中调用这个Service方法,返回`List<DCompanyNode>`。 2. Spring MVC会自动将这个列表转换为JSON格式(通过配置的HttpMessageConverter,如Jackson)。 但是,需要注意: - 确保`DCompanyNode`类中的字段可以被Jackson正确序列化(比如有getter方法,或者使用注解)。 - 避免循环引用:例如,父节点包含子节点,子节点又包含父节点,这会导致序列化无限循环。在`DCompanyNode`中,我们只有`parent`属性(存储父节点的ID,而不是整个父节点对象),所以不会出现循环引用。但是,子节点列表是完整的对象,因此需要确保子节点不会反向引用父节点对象(从代码看,子节点中并没有存储父节点对象,只有`parent`字符串,所以安全)。 控制层示例代码: ```java @Controller @RequestMapping("/company") public class CompanyController { @Autowired private CompanyService companyService; // 假设Service包含findforsurveyind方法 @RequestMapping(value = "/tree", method = RequestMethod.GET) @ResponseBody public List<DCompanyNode> getCompanyTree(Params params) { // 调用Service方法获取树形结构 return companyService.findforsurveyind(params); } } ``` 这样,当访问`/company/tree`时,将返回一个JSON数组,表示树形结构。每个节点的结构可能如下: ```json { "id": "node1", "parent": null, // 根节点的parent可能为null "checked": true, // 如果被选中 "state": "open", // 节点状态 "children": [ // 子节点数组 { "id": "node1_1", "parent": "node1", "checked": false, "state": "open", "children": [] } ] } ``` 注意:在返回给前端时,确保字段名与前端树形组件(如EasyUI combotree)的要求匹配。例如,combotree要求节点有`id`、`text`、`children`等字段。而我们的节点类中可能没有`text`字段(可能是`name`或其他),所以可能需要调整字段名或使用注解。 如果前端需要的字段名与`DCompanyNode`的字段名不一致,我们可以: 1. 修改`DCompanyNode`类,增加注解(如`@JsonProperty`)来指定序列化时的字段名。 2. 或者创建一个DTO(Data Transfer Object)类,将`DCompanyNode`转换为DTO,再返回给前端。 例如,前端combotree要求节点有`id`和`text`,我们可以这样创建DTO: ```java public class TreeNode { private String id; private String text; private boolean checked; private String state; private List<TreeNode> children; // 构造方法、getter和setter } ``` 然后在Service层或控制层进行转换。但是,由于原方法已经返回`List<DCompanyNode>`,并且我们不想改变原有逻辑,可以在控制层转换: ```java @RequestMapping(value = "/tree", method = RequestMethod.GET) @ResponseBody public List<TreeNode> getCompanyTree(Params params) { List<DCompanyNode> nodes = companyService.findforsurveyind(params); return convertToTreeNodes(nodes); } private List<TreeNode> convertToTreeNodes(List<DCompanyNode> nodes) { List<TreeNode> result = new ArrayList<>(); for (DCompanyNode node : nodes) { TreeNode treeNode = new TreeNode(); treeNode.setId(node.getId()); treeNode.setText(node.getName()); // 假设DCompanyNode有一个name字段用于文本 treeNode.setChecked(node.isChecked()); // 假设DCompanyNode有isChecked方法 treeNode.setState(node.getState()); // 递归转换子节点 if (node.getChildren() != null && !node.getChildren().isEmpty()) { treeNode.setChildren(convertToTreeNodes(node.getChildren())); } else { treeNode.setChildren(new ArrayList<>()); // 空子节点列表 } result.add(treeNode); } return result; } ``` 但是,注意原方法返回的`DCompanyNode`中,子节点列表已经构建好了(在`findforsurveyind`方法中通过`addDCompanyNode`添加),所以我们可以直接递归转换。 另外,关于性能:如果树很大,递归转换可能会导致栈溢出。我们可以使用非递归方式(如使用队列或栈)进行转换,但一般情况下树不会太深。 总结: - 理解`DCompanyNode`的结构,它已经是一个树形结构。 - 在控制层中,可以直接返回`List<DCompanyNode>`,但要注意字段名是否与前端匹配。 - 如果不匹配,则需要进行转换。 根据引用[1]中返回`Result<List<User>>`的示例,我们也可以返回一个包装结果的对象,如`Result<List<DCompanyNode>>`,以统一返回格式。 例如: ```java @RequestMapping(value = "/tree", method = RequestMethod.GET) @ResponseBody public Result<List<DCompanyNode>> getCompanyTree(Params params) { List<DCompanyNode> nodes = companyService.findforsurveyind(params); return Result.buildResult(Result.Status.OK, nodes); } ``` 其中`Result`是一个自定义的通用返回类。 调试建议: - 在控制层方法中设置断点,查看返回的`List<DCompanyNode>`结构是否正确。 - 使用Postman或浏览器访问接口,查看返回的JSON数据是否符合预期。 如果遇到问题,可以检查: - `DCompanyNode`类是否有正确的getter方法(Jackson通过getter获取字段值)。 - 是否有字段被忽略(使用`@JsonIgnore`或没有getter)。 - 循环引用问题(虽然我们分析没有,但如果有双向引用,需要使用`@JsonIgnore`忽略其中一个方向)。 最后,关于引用[2][3][4],虽然与当前问题不直接相关,但我们可以借鉴: - 引用[2]:如果需要对返回的列表进行排序,可以使用Stream流(但当前方法已经构建了树形结构,排序可能需要在构建树时进行)。 - 引用[3][4]:如果只需要返回简单的字符串列表,可以参考,但当前需求是树形结构。 因此,重点在于正确构建树形结构并正确序列化为JSON。 §§相关问题§§ 1. 如何解决Jackson序列化树形结构时的循环引用问题? 2. 如何优化大型树形数据的递归转换性能? 3. 前端树形组件(如combotree)对数据结构的具体要求有哪些? 4. 如何在Spring MVC中统一处理返回结果格式?</think>### Java方法返回的`List<DCompanyNode>`数据结构解析 #### 1. **`DCompanyNode`核心结构** 根据代码逻辑,`DCompanyNode`应包含以下关键属性: ```java public class DCompanyNode { private String id; // 节点唯一标识 private String parent; // 父节点ID(可为null) private boolean checked; // 选中状态 private String state; // 节点状态("open"/"closed") private List<DCompanyNode> children = new ArrayList<>(); // 子节点 // 核心方法 public void addDCompanyNode(DCompanyNode child) { children.add(child); } // Getters & Setters } ``` #### 2. **返回数据结构特点** - **树形嵌套结构**:父节点包含子节点列表(递归结构) - **状态控制**: - `checked=true`:用户选中的节点(来自`params.getString("surveyind")`) - `state="open"`:需要展开的节点 - **数据来源**: - `findForNodeList1CODE`:父节点集合(顶级节点) - `findForNodeList2CODE`:子节点集合(通过`parent`关联父节点) #### 3. **典型JSON输出示例** ```json [ { "id": "parent1", "parent": null, "checked": false, "state": "open", "children": [ { "id": "child1", "parent": "parent1", "checked": true, "state": "open", "children": [] } ] } ] ``` --- ### Spring MVC控制层使用方式 #### 1. **基础Controller实现** ```java @RestController @RequestMapping("/company") public class CompanyController { @Autowired private RfcUnderlaService service; // 包含findforsurveyind方法的Service @GetMapping("/nodes") public List<DCompanyNode> getCompanyNodes( @RequestParam String surveyind, @RequestParam(required = false) String otherParams) { // 构建参数对象 Params params = new Params(); params.set("surveyind", surveyind); // 调用Service方法 return service.findforsurveyind(params); } } ``` #### 2. **增强功能实现** **2.1 统一响应封装**(参考引用[1]) ```java @GetMapping("/nodes") public Result<List<DCompanyNode>> getNodes(Params params) { List<DCompanyNode> nodes = service.findforsurveyind(params); return Result.ok(nodes); // 统一响应格式 } // 统一响应类 public class Result<T> { private int code; private String msg; private T data; // 静态工厂方法 public static <T> Result<T> ok(T data) { return new Result<>(200, "success", data); } } ``` **2.2 数据转换优化**(参考引用[2][4]) ```java // 使用Stream转换数据结构 public List<Map<String, Object>> convertToMap(List<DCompanyNode> nodes) { return nodes.stream().map(node -> { Map<String, Object> map = new HashMap<>(); map.put("id", node.getId()); map.put("text", node.getName()); // 添加显示文本 map.put("checked", node.isChecked()); map.put("children", convertToMap(node.getChildren())); // 递归处理 return map; }).collect(Collectors.toList()); } ``` #### 3. **前端交互示例** ```javascript // 前端调用(jQuery示例) $.get('/company/nodes', { surveyind: '101,203' }, function(data) { $('#companyTree').tree({ data: data, onSelect: function(node) { console.log('选中节点:', node.id); } }); }); ``` --- ### 关键调试建议 1. **验证数据结构**: ```java // 在Controller中添加调试输出 System.out.println("返回节点数: " + nodes.size()); nodes.forEach(node -> { System.out.println("父节点: " + node.getId() + " 子节点数: " + node.getChildren().size()); }); ``` 2. **检查JSON序列化**: - 确保`DCompanyNode`有完整的getter方法 - 排除循环引用(如`@JsonIgnore`标注双向关联) 3. **空集合处理**: ```java // 防止children=null导致前端解析错误 public List<DCompanyNode> getChildren() { return children != null ? children : Collections.emptyList(); } ``` --- ### 常见问题解决方案 | 问题现象 | 解决方案 | |---------|----------| | 前端无法展开节点 | 确保`state`字段返回"open" | | 选中状态不生效 | 检查`params.getString("surveyind")`格式(逗号分隔ID) | | 循环引用异常 | 在`parent`字段添加`@JsonIgnore` | | 子节点丢失 | 验证DAO查询`findForNodeList2CODE`是否返回完整数据 | > **关键点**:树形结构需确保每个节点都有完整的父子关系链,顶级节点的`parent`应为`null`[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值