知乎专栏 | 多维度架构 |
@RequestMapping("/get/{id}") public Member getStatistics(@PathVariable long id) { Member statistics = memberRepostitory.findOne(id); if (statistics == null) { statistics = new Member(); } return statistics; }
MediaType.APPLICATION_JSON_VALUE 执行结果反馈json数据
@RestController @RequestMapping("/api/persons") public class MainController { @RequestMapping( value = "/detail/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE ) public ResponseEntity<Persons> getUserDetail(@PathVariable Long id) { Persons user = personsRepository.findById(id); return new ResponseEntity<>(user, HttpStatus.OK); } }
@RequestMapping(value = "/create", method = RequestMethod.POST, produces = { "application/xml", "application/json" }) public ResponseEntity<Member> create(@RequestBody Member member) { memberRepository.save(member); return new ResponseEntity<Member>(member, HttpStatus.OK); }
package api.config; import java.io.IOException; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; @Configuration public class JacksonConfig { @Bean @Primary @ConditionalOnMissingBean(ObjectMapper.class) public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { jsonGenerator.writeString(""); } }); return objectMapper; } }
restful 将同时支持 json 和 xml 数据传递
package com.example.api.restful; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.api.domain.RecentRead; import com.example.api.repository.RecentReadRepostitory; @RestController @RequestMapping("/restful/article") public class ArticleRestController { @Autowired private RecentReadRepostitory recentReadRepostitory; @RequestMapping(value = "/recent/read/add/{memberId}/{articleId}", method = RequestMethod.GET, produces = { "application/xml", "application/json" }) public ResponseEntity<RecentRead> recentAdd(@PathVariable long memberId, @PathVariable long articleId) { RecentRead recentRead = new RecentRead(); recentRead.setMemberId(memberId); recentRead.setArticleId(articleId); recentReadRepostitory.save(recentRead); return new ResponseEntity<RecentRead>(recentRead, HttpStatus.OK); } @RequestMapping(value="/recent/read/list/{id}", produces = { "application/xml", "application/json" }) public List<RecentRead> recentList(@PathVariable long id) { int page = 0; int limit = 20; List<RecentRead> recentRead = recentReadRepostitory.findByMemberId(id, new PageRequest(page, limit)); return recentRead; } }
开发中发现很多人不适应新的接口方式,有时候只能妥协,这些顽固不化的人需要这样的数据库格式
{ "status":true, "reason":"登录成功", "code":1, "data":{ "id":2, "name":null, "sex":null, "age":0, "wechat":null, "mobile":"13113668890", "picture":null, "ipAddress":"0:0:0:0:0:0:0:1" } }
返回数据必须放在 data 字典中, 而我通常是采用 http status code 来返回状态,返回结果是对象。实现上面的需求我们需要加入一个data成员变量,因为我们不清楚最终要返回什么对象。所以声明为 java.lang.Object
package com.example.api.pojo; import java.io.Serializable; public class RestfulResponse implements Serializable { /** * */ private static final long serialVersionUID = -4045645995352698349L; private boolean status; private String reason; private int code; private Object data; public RestfulResponse(boolean status, int code, String reason, Object data) { this.status = status; this.code = code; this.reason = reason; this.data = data; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } @Override public String toString() { return "RestfulResponse [status=" + status + ", reason=" + reason + ", code=" + code + ", data=" + data + "]"; } }
Service
public RestfulResponse bindWechat(String mobile, String wechat) { Member member = memberRepository.findByMobile(mobile); member.setWechat(wechat); memberRepository.save(member); return new RestfulResponse(true, 1, "微信绑定成功", member); }
Controller
@RequestMapping("/login/sms/{mobile}/{code}") public RestfulResponse sms(@PathVariable String mobile, @PathVariable String wechat) { return memberService.bindWechat(mobile, wechat); }
@RequestMapping(value = "/list", method=RequestMethod.GET) public Page<Blog> getEntryByPageable1(@PageableDefault( sort = { "id" }, direction = Sort.Direction.DESC) Pageable pageable) { return blogRepository.findAll(pageable); } @RequestMapping(value = "/blog", method=RequestMethod.GET) public Page<Blog> getEntryByPageable(@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC) Pageable pageable) { return blogRepository.findAll(pageable); } @RequestMapping(value = "/list", method=RequestMethod.GET) public Page<Blog> getEntryByPageable2(@PageableDefault Pageable pageable) { return blogRepository.findAll(pageable); } @ModelAttribute("users") public Page<User> users(@PageableDefault(size = 5) Pageable pageable) { return userManagement.findAll(pageable); }
我们只需要在方法的参数中直接定义一个pageable类型的参数,当Spring发现这个参数时,Spring会自动的根据request的参数来组装该pageable对象,Spring支持的request参数如下: page,第几页,从0开始,默认为第0页 size,每一页的大小,默认为20 sort,排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列 这样,我们就可以通过url的参数来进行多样化、个性化的查询,而不需要为每一种情况来写不同的方法了。 通过url来定制pageable很方便,但唯一的缺点是不太美观,因此我们需要为pageable设置一个默认配置,这样很多情况下我们都能够通过一个简洁的url来获取信息了。 Spring提供了@PageableDefault帮助我们个性化的设置pageable的默认配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)表示默认情况下我们按照id倒序排列,每一页的大小为15。
spring.servlet.multipart.max-file-size=128KB spring.servlet.multipart.max-request-size=128KB spring.http.multipart.enabled=false
RestController
package api.restful; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import api.pojo.RestfulResponse; @RestController @RequestMapping("/upload") public class UploadRestController { private final static String FOLDER = "/tmp"; public UploadRestController() { } @PostMapping("/single") public RestfulResponse upload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) { return new RestfulResponse(false, 0, "Please select a file to upload", ""); } try { byte[] bytes = file.getBytes(); Path path = Paths.get(FOLDER + "/" + file.getOriginalFilename()); Files.write(path, bytes); return new RestfulResponse(true, 0, "", path.toString()); } catch (Exception e) { return new RestfulResponse(false, 0, e.getMessage(), null); } } @PostMapping(value = "/group") public RestfulResponse group(@RequestParam("files") MultipartFile[] files) { List<String> filelist = new ArrayList<String>(); try { for (MultipartFile file : files) { File tmpfile = new File(FOLDER + "/" + file.getOriginalFilename()); file.transferTo(tmpfile); filelist.add(tmpfile.getPath()); } return new RestfulResponse(true, 0, null, filelist); } catch (Exception e) { return new RestfulResponse(false, 0, e.getMessage(), null); } } }
由于上传文件名可能存在空格等特殊字符,这里使用UUID替代文件名
@PostMapping(value = "/file") public RestfulResponse file(@RequestParam("file") MultipartFile[] files) { List<Object> filelist = new ArrayList<Object>(); try { for (MultipartFile file : files) { UUID uuid = UUID.randomUUID(); String filename = String.format("%s/%s.%s", folder, uuid.toString(), this.getExtensionName(filename)); File tmpfile = new File(filename); String filepath = tmpfile.getPath(); System.out.println(filepath); file.transferTo(tmpfile); filelist.add(tmpfile.toString()); } return new RestfulResponse(true, 0, null, filelist); } catch (Exception e) { return new RestfulResponse(false, 0, e.getMessage(), null); } } private String getExtensionName(String filename) { if ((filename != null) && (filename.length() > 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length() - 1))) { return filename.substring(dot + 1); } } return filename; }
下面是一个导出 CSV 文件的例子
@GetMapping("/export") public void export(HttpServletResponse response) throws IOException { response.setContentType("application/csv"); // response.setContentType("application/csv;charset=gb18030"); response.setHeader("Content-Disposition", "attachment; filename=\"file.csv\""); BufferedWriter writer = new BufferedWriter(response.getWriter()); // 需要写入 utf8bom 头否则会出现中文乱码 // byte[] uft8bom = { (byte) 0xef, (byte) 0xbb, (byte) 0xbf }; String bom = new String(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF }); writer.write(bom); writer.write("A,B,C"); writer.newLine(); tableRepository.findAll().forEach(table -> { try { String tmp = String.format("%s,%s,%s", table.getId(), table.getMethod(), table.getMoney()); writer.write(tmp); writer.newLine(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); writer.flush(); writer.close(); }
#序列化时间格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.mvc.date-format=yyyy-MM-dd HH:mm:ss #mvc序列化时候时区选择 spring.jackson.time-zone=GMT+8
@JsonIgnore private String entityName = this.getClass().getSimpleName();
默认 json 中的时间格式是这样的
"createDate":"2018-09-11T07:34:20.106+0000","updateDate":"2018-09-11T07:34:20.106+0000"
@JsonFormat 可以格式化 json 返回的时间格式。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
格式化后
"createDate":"2018-09-11 07:42:44","updateDate":"2018-09-11 07:42:44"
解决时区问题,MongoDb 默认使用UTC,显示时间相差8小时
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Date createdDate = new Date();
public class Test { @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MMM-dd HH:mm:ss z", timezone="EST") @JsonProperty("pubDate") private Date recentBookPubDate; }
public class Test { @JsonFormat(shape=JsonFormat.Shape.NUMBER) @JsonProperty("birthDate") private Date birthDate; }
{ "birthDate" : 1528702883858 }
package cn.netkiller.json; public class Member { private String name; public Member() { // TODO Auto-generated constructor stub } public Member(String name) { // TODO Auto-generated constructor stub this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Member [name=" + name + "]"; } }
package cn.netkiller.json; import java.io.IOException; import org.springframework.boot.jackson.JsonComponent; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.TextNode; @JsonComponent public class Json { public Json() { // TODO Auto-generated constructor stub } public static class MemberJsonSerializer extends JsonSerializer<Member> { @Override public void serialize(Member value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // TODO Auto-generated method stub gen.writeStartObject(); gen.writeStringField("member", value.toString()); gen.writeEndObject(); } } public static class MemberJsonDeserializer extends JsonDeserializer<Member> { @Override public Member deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { // TODO Auto-generated method stub TreeNode treeNode = p.getCodec().readTree(p); TextNode member = (TextNode) treeNode.get("member"); return new Member(member.asText()); } } }
package cn.netkiller.json.controller; import cn.netkiller.json.Member; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * * @author neo */ @RestController public class SimpleController { @Autowired public ObjectMapper objectMapper; @GetMapping("/") public String home() throws JsonMappingException, JsonProcessingException { String json = "{\"name\":\"netkiller\"}"; Member member = objectMapper.readValue(json, Member.class); System.out.println(member.getName()); return member.getName(); } }
ObjectMapper mapper = new ObjectMapper(); User user = new User(); //Object to JSON in file mapper.writeValue(new File("c:\\user.json"), user); //Object to JSON in String String jsonInString = mapper.writeValueAsString(user); //Convert object to JSON string and pretty print String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
Notification notification = new Notification(status, time, summary + info); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(notification);
避免接口无序执行,被同时多次执行,同一时间只能有一个请求,请求完毕之后才能进行下一次请求。
@GetMapping("/lock/{id}") public String lock1(@PathVariable("id") String id) throws InterruptedException { synchronized (id.intern()) { log.info(Thread.currentThread().getName() + " 上锁"); Thread.sleep(10000); log.info(Thread.currentThread().getName() + " 解锁"); } return Thread.currentThread().getName(); }
使用 ConcurrentHashMap 数据共享
private final Map<String, Object> share = new ConcurrentHashMap<>(); @GetMapping("/share/{id}") public Map<String, Object> shareTest(@PathVariable("id") String id) throws InterruptedException { // share.computeIfAbsent(id, k -> new Object()); share.computeIfAbsent(id, key -> { return new Date(); }); synchronized (share) { log.info(Thread.currentThread().getName() + " 上锁"); Thread.sleep(1000); log.info(Thread.currentThread().getName() + " 解锁"); } return share; }