Spring Boot 学习笔记 - 钢钢更新
背景介绍
该文档是在慕课网实战课程《Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。
该文档作为个人参考资料,会长期更新。
慕课网课程地址:Spring Boot企业微信点餐系统
数据库设计
微信点餐数据库 - SQL.md
-- 类目 create table `product_category` ( `category_id` int not null auto_increment, `category_name` varchar(64) not null comment '类目名字', `category_type` int not null comment '类目编号', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`category_id`) ); -- 商品 create table `product_info` ( `product_id` varchar(32) not null, `product_name` varchar(64) not null comment '商品名称', `product_price` decimal(8,2) not null comment '单价', `product_stock` int not null comment '库存', `product_description` varchar(64) comment '描述', `product_icon` varchar(512) comment '小图', `product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架', `category_type` int not null comment '类目编号', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`product_id`) ); -- 订单 create table `order_master` ( `order_id` varchar(32) not null, `buyer_name` varchar(32) not null comment '买家名字', `buyer_phone` varchar(32) not null comment '买家电话', `buyer_address` varchar(128) not null comment '买家地址', `buyer_openid` varchar(64) not null comment '买家微信openid', `order_amount` decimal(8,2) not null comment '订单总金额', `order_status` tinyint(3) not null default '0' comment '订单状态, 默认为新下单', `pay_status` tinyint(3) not null default '0' comment '支付状态, 默认未支付', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`order_id`), key `idx_buyer_openid` (`buyer_openid`) ); -- 订单商品 create table `order_detail` ( `detail_id` varchar(32) not null, `order_id` varchar(32) not null, `product_id` varchar(32) not null, `product_name` varchar(64) not null comment '商品名称', `product_price` decimal(8,2) not null comment '当前价格,单位分', `product_quantity` int not null comment '数量', `product_icon` varchar(512) comment '小图', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`detail_id`), key `idx_order_id` (`order_id`) ); -- 卖家(登录后台使用, 卖家登录之后可能直接采用微信扫码登录,不使用账号密码) create table `seller_info` ( `id` varchar(32) not null, `username` varchar(32) not null, `password` varchar(32) not null, `openid` varchar(64) not null comment '微信openid', `create_time` timestamp not null default current_timestamp comment '创建时间', `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间', primary key (`id`) ) comment '卖家信息表';
Spring Boot项目结构
POM依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.imooc</groupId> <artifactId>sell</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sell</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>cn.springboot</groupId> <artifactId>best-pay-sdk</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-starter-cache</artifactId>--> <!--</dependency>--> </dependencies> <build> <finalName>sell</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
应用配置
全局配置文件
application.yml
spring: profiles: active: dev
开发配置文件
application-dev.yml
spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false jpa: show-sql: true jackson: default-property-inclusion: non_null redis: host: 192.168.30.113 port: 6379 server: context-path: /sell #logging: # pattern: # console: "%d - %msg%n" ## path: /var/log/tomcat/ # file: /var/log/tomcat/sell.log # level: # com.imooc.LoggerTest: debug wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify templateId: orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ projectUrl: wechatMpAuthorize: http://sell.natapp4.cc wechatOpenAuthorize: http://sell.natapp4.cc sell: http://sell.natapp4.cc logging: level: com.imooc.dataobject.mapper: trace mybatis: mapper-locations: classpath:mapper/*.xml
生产配置文件
application-prod.yml
spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false jackson: default-property-inclusion: non_null redis: host: 192.168.30.113 port: 6379 server: context-path: /sell #logging: # pattern: # console: "%d - %msg%n" ## path: /var/log/tomcat/ # file: /var/log/tomcat/sell.log # level: # com.imooc.LoggerTest: debug wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify templateId: orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ projectUrl: wechatMpAuthorize: http://sell.natapp4.cc wechatOpenAuthorize: http://sell.natapp4.cc sell: http://sell.natapp4.cc logging: level: com.imooc.dataobject.mapper: trace mybatis: mapper-locations: classpath:mapper/*.xml
配置文件类
BlogProperties.java
package com.mindex.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @ConfigurationProperties(prefix = "com.mindex.blog") @Component public class BlogProperties { private String name; private String desc; }
引用配置信息
BlogPropertiesTest.java
package com.mindex.config; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class BlogPropertiesTest { @Autowired private BlogProperties blogProperties; @Test public void getProperties() throws Exception { Assert.assertEquals("轮子王", blogProperties.getName()); Assert.assertEquals("用行动改变世界", blogProperties.getDesc()); } }
自定义配置文件
WechatAccountConfig.java
package com.imooc.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; @Data @Component @ConfigurationProperties(prefix = "wechat") public class WechatAccountConfig { private String mpAppId; private String mpAppSecret; private String openAppId; private String openAppSecret; private String mchId; private String mchKey; private String keyPath; private String notifyUrl; private Map<String, String> templateId; }
引用自定义的配置文件
WechatMpConfig.java
package com.imooc.config; import me.chanjar.weixin.mp.api.WxMpConfigStorage; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class WechatMpConfig { @Autowired private WechatAccountConfig accountConfig; @Bean public WxMpService wxMpService() { WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxMpConfigStorage()); return wxMpService; } @Bean public WxMpConfigStorage wxMpConfigStorage() { WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage(); wxMpConfigStorage.setAppId(accountConfig.getMpAppId()); wxMpConfigStorage.setSecret(accountConfig.getMpAppSecret()); return wxMpConfigStorage; } }
日志处理
SLF4j
Logback
logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern> %d - %msg%n </pattern> </layout> </appender> <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>DENY</onMatch> <onMismatch>ACCEPT</onMismatch> </filter> <encoder> <pattern> %msg%n </pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>/Users/kwang/imooc/sell/log/info.%d.log</fileNamePattern> </rollingPolicy> </appender> <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <encoder> <pattern> %msg%n </pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>/Users/kwang/imooc/sell/log/error.%d.log</fileNamePattern> </rollingPolicy> </appender> <root level="info"> <appender-ref ref="consoleLog" /> <appender-ref ref="fileInfoLog" /> <appender-ref ref="fileErrorLog" /> </root> </configuration>
Swagger2 文档工具
引入POM依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency>
创建Swagger配置类
package com.mindex.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2Configuration { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() // 自行修改为自己的包路径 .apis(RequestHandlerSelectors.basePackage("com.mindex.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("api文档") .description("Restful 风格接口") .version("1.0") .build(); } }
在controller中引入Swagger注解
package com.mindex.controller; import com.mindex.entities.User; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping(value = "/users") @Api(value = "/users", tags = "测试接口模块") public class UserController { //创建线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @ApiOperation(value = "获取用户列表", notes = "") @RequestMapping(value = "/", method = RequestMethod.GET) public List<User> getUserList() { // 处理"/users/"的GET请求,用来获取用户列表 // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 List<User> userList = new ArrayList<User>(users.values()); return userList; } @ApiOperation(value = "创建用户", notes = "根据User对象创建用户") @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") @RequestMapping(value = "/", method = RequestMethod.POST) public String postUser(@ModelAttribute User user) { // 处理"/users/"的POST请求,用来创建User // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数 users.put(user.getId(), user); return "success"; } @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value = "/{id}", method = RequestMethod.GET) public User getUser(@PathVariable Long id) { // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 // url中的id可通过@PathVariable绑定到函数的参数中 return users.get(id); } @ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User") }) @RequestMapping(value = "/{id}", method = RequestMethod.POST) public String putUser(@PathVariable Long id, @ModelAttribute User user) { // 处理"/users/{id}"的PUT请求,用来更新User信息 User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 处理"/users/{id}"的DELETE请求,用来删除User users.remove(id); return "success"; } }
启动tomcat查看文档
IDEA插件
JRebel插件
引入POM依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>1.5.1.RELEASE</version> <scope>provided</scope> </dependency>
配置Application.java
@SpringBootApplication @ComponentScan(basePackages = "com.mindex") public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Application.class); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
配置maven project选项
选中Lifecycle-clean及compile
安装JRebel插件
配置JRebel插件
在IDEA里新建一个部署配置项。
运行测试
Lombok插件
好处:安装了Lombok插件和pom引用依赖后,可以简化代码,例如:无需再写get/set/toString方法,打印日志时直接使用log关键字等。
安装Lombok插件
引用pom依赖
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
好处1:只要使用@Data、@Getter、@Setter、@ToString等注解,无需再写繁琐的get/set/toString方法,Lombok会在编译时自动加入代码。
package com.imooc.dataobject; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Date; @Entity @DynamicUpdate @Data public class ProductCategory { @Id @GeneratedValue private Integer categoryId; private String categoryName; private Integer categoryType; private Date createTime; private Date updateTime; public ProductCategory(String categoryName, Integer categoryType) { this.categoryName = categoryName; this.categoryType = categoryType; } public ProductCategory() { } }
好处2:输出日志时,可以直接使用log关键字输出,支持参数引用。
package com.imooc; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = LoggerTest.class) @Slf4j public class LoggerTest { // 无需再写LoggerFactory // private final Logger logger = LoggerFactory.getLogger(LoggerTest.class); @Test public void test1() { String name = "imooc"; String password = "12345"; log.debug("debug..."); log.info("name: {}, password: {}", name, password); log.error("error..."); log.warn("warning..."); } }
项目运行类(主入口)
SellApplication.java
package com.imooc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SellApplication { public static void main(String[] args) { SpringApplication.run(SellApplication.class, args); } }
enums枚举类
ResultEnum.java
package com.imooc.enums; import lombok.Getter; @Getter public enum ResultEnum { PARAM_ERROR(1,"参数不正确"), PRODUCT_NOT_EXIST(10, "商品不存在"), PRODUCT_STOCK_ERROR(11, "库存不正确"), ORDER_NOT_EXIST(12, "订单不存在"), ; private Integer code; private String message; ResultEnum(Integer code, String message) { this.code = code; this.message = message; } }
util工具类
可以把常用的方法放在util包里,比如拼接vo视图、生成唯一编码等;
构造结果VO视图
ResultVOUtil.java
package com.imooc.utils; import com.imooc.VO.ResultVO; public class ResultVOUtil { public static ResultVO success(Object object) { ResultVO resultVO = new ResultVO(); resultVO.setData(object); resultVO.setCode(0); resultVO.setMsg("成功"); return resultVO; } public static ResultVO success() { return success(null); } public static ResultVO error(Integer code, String msg) { ResultVO resultVO = new ResultVO(); resultVO.setCode(code); resultVO.setMsg(msg); return resultVO; } }
生成随机id
KeyUtil.java
package com.imooc.utils; import java.util.Random; public class KeyUtil { /** * 生成唯一的主键 * 格式: 时间+随机数 * * @return */ public static synchronized String genUniqueKey() { Random random = new Random(); Integer number = random.nextInt(900000) + 100000; return System.currentTimeMillis() + String.valueOf(number); } }
object -> json
JsonUtil.java
package com.imooc.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class JsonUtil { public static String toJson(Object object) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); return gson.toJson(object); } }
Cookie工具类
CookieUtil.java
package com.imooc.utils; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; public class CookieUtil { /** * 设置 * @param response * @param name * @param value * @param maxAge */ public static void set(HttpServletResponse response, String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setMaxAge(maxAge); response.addCookie(cookie); } /** * 获取cookie * @param request * @param name * @return */ public static Cookie get(HttpServletRequest request, String name) { Map<String, Cookie> cookieMap = readCookieMap(request); if (cookieMap.containsKey(name)) { return cookieMap.get(name); }else { return null; } } /** * 将cookie封装成Map * @param request * @return */ private static Map<String, Cookie> readCookieMap(HttpServletRequest request) { Map<String, Cookie> cookieMap = new HashMap<>(); Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie: cookies) { cookieMap.put(cookie.getName(), cookie); } } return cookieMap; } }
比较金额(double类型)是否相等
MathUtil.java
package com.imooc.utils; public class MathUtil { private static final Double MONEY_RANGE = 0.01; /** * 比较2个金额是否相等 * @param d1 * @param d2 * @return */ public static Boolean equals(Double d1, Double d2) { Double result = Math.abs(d1 - d2); if (result < MONEY_RANGE) { return true; }else { return false; } } }
VO视图层
要返回的数据格式如下:
第一层VO
ResultVO.java
package com.imooc.VO; import lombok.Data; /** * http请求返回的最外层对象 */ @Data public class ResultVO<T> { /* 状态码 */ private Integer code; /* 提示信息 */ private String msg; /* 具体内容 */ private T data; }
第二层VO
ProductVO.java
package com.imooc.VO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * 商品(包含类目) */ @Data public class ProductVO { @JsonProperty("name") private String categoryName; @JsonProperty("type") private Integer categoryType; @JsonProperty("foods") private List<ProductInfoVO> productInfoVOList; }
第三层VO
ProductInfoVO.java
package com.imooc.VO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.math.BigDecimal; /** * 商品详情 */ @Data public class ProductInfoVO { @JsonProperty("id") private String productId; @JsonProperty("name") private String productName; @JsonProperty("price") private BigDecimal productPrice; @JsonProperty("description") private String productDescription; @JsonProperty("icon") private String productIcon; }
DTO层
可以把DTO理解成数据库视图。
OrderDTO.java
package com.imooc.dto; import com.imooc.dataobject.OrderDetail; import lombok.Data; import java.math.BigDecimal; import java.util.Date; import java.util.List; @Data public class OrderDTO { List<OrderDetail> orderDetailList; private String orderId; private String buyerName; private String buyerPhone; private String buyerAddress; private String buyerOpenid; private BigDecimal orderAmount; private Integer orderStatus; private Integer payStatus; private Date createTime; private Date updateTime; }
CartDTO.java
package com.imooc.dto; import lombok.Data; @Data public class CartDTO { private String productId; private Integer productQuantity; public CartDTO(String productId, Integer productQuantity) { this.productId = productId; this.productQuantity = productQuantity; } }
Exception异常处理
自定义异常
SellException.java
package com.imooc.exception; import com.imooc.enums.ResultEnum; public class SellException extends RuntimeException { private Integer code; public SellException(ResultEnum resultEnum) { super(resultEnum.getMessage()); this.code = resultEnum.getCode(); } public SellException(Integer code, String message) { super(message); this.code = code; } }
ResponseBankException.java
package com.imooc.exception; public class ResponseBankException extends RuntimeException { }
SellerAuthorizeException.java
package com.imooc.exception; public class SellerAuthorizeException extends RuntimeException { }
自定义异常处理器
SellExceptionHandler.java
package com.imooc.handler; import com.imooc.VO.ResultVO; import com.imooc.config.ProjectUrlConfig; import com.imooc.exception.ResponseBankException; import com.imooc.exception.SellException; import com.imooc.exception.SellerAuthorizeException; import com.imooc.utils.ResultVOUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class SellExceptionHandler { @Autowired private ProjectUrlConfig projectUrlConfig; //拦截登录异常 //http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login @ExceptionHandler(value = SellerAuthorizeException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public ModelAndView handlerAuthorizeException() { return new ModelAndView("redirect:" .concat(projectUrlConfig.getWechatOpenAuthorize()) .concat("/sell/wechat/qrAuthorize") .concat("?returnUrl=") .concat(projectUrlConfig.getSell()) .concat("/sell/seller/login")); } @ExceptionHandler(value = SellException.class) @ResponseBody public ResultVO handlerSellerException(SellException e) { return ResultVOUtil.error(e.getCode(), e.getMessage()); } @ExceptionHandler(value = ResponseBankException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public void handleResponseBankException() { } }
统一异常处理
假设访问一个不存在的页面,抛出异常
package com.mindex.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class ThymeleafTest { @ResponseBody @RequestMapping("/hello") public String hello() throws Exception { throw new Exception("发生错误"); } }
创建全局异常处理类
通过使用@ControllerAdvice
定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler
用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html
中。
package com.mindex.exception; import com.mindex.entities.ErrorInfo; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
实现error.html
页面展示:在templates
目录下创建error.html
,将请求的URL和Exception对象的message输出。
<!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head lang="en"> <meta charset="UTF-8"/> <title>统一异常处理</title> </head> <body> <h1>Error Handler</h1> <div th:text="${url}"></div> <div th:text="${exception.message}"></div> </body> </html>
启动该应用,访问:http://localhost:8080/hello
,可以看到如下错误提示页面。
通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice
类中,根据抛出的具体Exception类型匹配@ExceptionHandler
中配置的异常类型来匹配错误映射和处理。
返回json格式
创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据。
package com.mindex.entities; import lombok.Data; @Data public class ErrorInfo<T> { public static final Integer OK = 0; public static final Integer ERROR = 100; private Integer code; private String message; private String url; private T data; }
创建一个自定义异常,用来实验捕获该异常,并返回json。
package com.mindex.exception; public class MyException extends Exception { public MyException(String message) { super(message); } }
Controller
中增加json映射,抛出MyException
异常。
package com.mindex.controller; import com.mindex.exception.MyException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/json") public String json() throws MyException { throw new MyException("发生错误2"); } }
为MyException
异常创建对应的处理。
@ExceptionHandler(value = MyException.class) @ResponseBody public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception { ErrorInfo<String> r = new ErrorInfo<>(); r.setMessage(e.getMessage()); r.setCode(ErrorInfo.ERROR); r.setData("Some Data"); r.setUrl(req.getRequestURL().toString()); return r; }
启动应用,访问:http://localhost:8080/json
,可以得到如下返回内容:
Authorize用户有效性鉴定
SellerAuthorizeAspect.java
package com.imooc.aspect; import com.imooc.constant.CookieConstant; import com.imooc.constant.RedisConstant; import com.imooc.exception.SellerAuthorizeException; import com.imooc.utils.CookieUtil; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @Aspect @Component @Slf4j public class SellerAuthorizeAspect { @Autowired private StringRedisTemplate redisTemplate; @Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" + "&& !execution(public * com.imooc.controller.SellerUserController.*(..))") public void verify() { } @Before("verify()") public void doVerify() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //查询cookie Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN); if (cookie == null) { log.warn("【登录校验】Cookie中查不到token"); throw new SellerAuthorizeException(); } //去redis里查询 String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue())); if (StringUtils.isEmpty(tokenValue)) { log.warn("【登录校验】Redis中查不到token"); throw new SellerAuthorizeException(); } } }
Data Object层(Entity)
主要用来映射数据库表及字段关系。
dataobject定义
ProductCategory.java
package com.imooc.dataobject; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Date; @Entity @DynamicUpdate @Data public class ProductCategory { @Id @GeneratedValue private Integer categoryId; private String categoryName; private Integer categoryType; private Date createTime; private Date updateTime; public ProductCategory(String categoryName, Integer categoryType) { this.categoryName = categoryName; this.categoryType = categoryType; } public ProductCategory() { } }
Repository层
JpaRepository对数据库常用操作进行了封装,通过继承JpaRepository可以快速实现数据库操作。
JpaRepository第一个参数是data object,第二个是该data object的主键。
repository定义
ProductCategoryRepository.java
package com.imooc.repository; import com.imooc.dataobject.ProductCategory; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> { List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); }
repository单元测试
ProductCategoryRepositoryTest.java
package com.imooc.repository; import com.imooc.dataobject.ProductCategory; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class ProductCategoryRepositoryTest { @Autowired private ProductCategoryRepository repository; @Test public void findOneTest() { ProductCategory productCategory = repository.findOne(1); System.out.println(productCategory.toString()); } @Test @Transactional public void saveTest() { ProductCategory productCategory = new ProductCategory("男生最爱", 4); ProductCategory result = repository.save(productCategory); Assert.assertNotNull(result); // Assert.assertNotEquals(null, result); } @Test public void findByCategoryTypeInTest() { List<Integer> list = Arrays.asList(2,3,4); List<ProductCategory> result = repository.findByCategoryTypeIn(list); Assert.assertNotEquals(0, result.size()); } @Test public void updateTest() { // ProductCategory productCategory = repository.findOne(4); // productCategory.setCategoryName("男生最爱1"); ProductCategory productCategory = new ProductCategory("男生最爱", 4); ProductCategory result = repository.save(productCategory); Assert.assertEquals(productCategory, result); } }
Service层
service接口
CategoryService.java
package com.imooc.service; import com.imooc.dataobject.ProductCategory; import java.util.List; public interface CategoryService { ProductCategory findOne(Integer categoryId); List<ProductCategory> findAll(); List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); ProductCategory save(ProductCategory productCategory); }
service实现
需要使用@Service注解
CategoryServiceImpl.java
package com.imooc.service.impl; import com.imooc.dataobject.ProductCategory; import com.imooc.repository.ProductCategoryRepository; import com.imooc.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class CategoryServiceImpl implements CategoryService { @Autowired private ProductCategoryRepository repository; @Override public ProductCategory findOne(Integer categoryId) { return repository.findOne(categoryId); } @Override public List<ProductCategory> findAll() { return repository.findAll(); } @Override public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) { return repository.findByCategoryTypeIn(categoryTypeList); } @Override public ProductCategory save(ProductCategory productCategory) { return repository.save(productCategory); } }
service单元测试
CategoryServiceImplTest.java
package com.imooc.service.impl; import com.imooc.dataobject.ProductCategory; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class CategoryServiceImplTest { @Autowired private CategoryServiceImpl categoryService; @Test public void findOne() throws Exception { ProductCategory productCategory = categoryService.findOne(1); Assert.assertEquals(new Integer(1), productCategory.getCategoryId()); } @Test public void findAll() throws Exception { List<ProductCategory> productCategoryList = categoryService.findAll(); Assert.assertNotEquals(0, productCategoryList.size()); } @Test public void findByCategoryTypeIn() throws Exception { List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(Arrays.asList(1,2,3,4)); Assert.assertNotEquals(0, productCategoryList.size()); } @Test public void save() throws Exception { ProductCategory productCategory = new ProductCategory("男生专享", 10); ProductCategory result = categoryService.save(productCategory); Assert.assertNotNull(result); } }
Controller层
@RestController 注解,直接返回json格式;
@RequestMapping("buyer/product") 注解声明服务的url前缀;
BuyerProductController.java
package com.imooc.controller; import com.imooc.VO.ProductInfoVO; import com.imooc.VO.ProductVO; import com.imooc.VO.ResultVO; import com.imooc.dataobject.ProductCategory; import com.imooc.dataobject.ProductInfo; import com.imooc.service.CategoryService; import com.imooc.service.ProductService; import com.imooc.utils.ResultVOUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping("/buyer/product") public class BuyerProductController { @Autowired private ProductService productService; @Autowired private CategoryService categoryService; @GetMapping("/list") public ResultVO list() { //1. 查询所有上架商品 List<ProductInfo> productInfoList = productService.findUpAll(); //2. 查询类目(一次性查询) //传统方法 List<Integer> categoryTypeList = new ArrayList<>(); // for (ProductInfo productInfo : productInfoList) { // categoryTypeList.add(productInfo.getCategoryType()); // } //精简方法(java8, lambda) categoryTypeList = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList()); List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList); //3. 数据拼装 List<ProductVO> productVOList = new ArrayList<>(); for (ProductCategory productCategory : productCategoryList) { ProductVO productVO = new ProductVO(); productVO.setCategoryName(productCategory.getCategoryName()); productVO.setCategoryType(productCategory.getCategoryType()); List<ProductInfoVO> productInfoVOList = new ArrayList<>(); for (ProductInfo productInfo : productInfoList) { if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) { ProductInfoVO productInfoVO = new ProductInfoVO(); BeanUtils.copyProperties(productInfo, productInfoVO); productInfoVOList.add(productInfoVO); } } productVO.setProductInfoVOList(productInfoVOList); productVOList.add(productVO); } return ResultVOUtil.success(productVOList); } }
SellerProductController.java
package com.imooc.controller; import com.imooc.dataobject.ProductCategory; import com.imooc.dataobject.ProductInfo; import com.imooc.exception.SellException; import com.imooc.form.ProductForm; import com.imooc.service.CategoryService; import com.imooc.service.ProductService; import com.imooc.utils.KeyUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; 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.servlet.ModelAndView; import javax.validation.Valid; import java.util.List; import java.util.Map; @Controller @RequestMapping("/seller/product") public class SellerProductController { @Autowired private ProductService productService; @Autowired private CategoryService categoryService; /** * 列表 * @param page * @param size * @param map * @return */ @GetMapping("/list") public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "size", defaultValue = "10") Integer size, Map<String, Object> map) { PageRequest request = new PageRequest(page - 1, size); Page<ProductInfo> productInfoPage = productService.findAll(request); map.put("productInfoPage", productInfoPage); map.put("currentPage", page); map.put("size", size); return new ModelAndView("product/list", map); } /** * 商品上架 * @param productId * @param map * @return */ @RequestMapping("/on_sale") public ModelAndView onSale(@RequestParam("productId") String productId, Map<String, Object> map) { try { productService.onSale(productId); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } /** * 商品下架 * @param productId * @param map * @return */ @RequestMapping("/off_sale") public ModelAndView offSale(@RequestParam("productId") String productId, Map<String, Object> map) { try { productService.offSale(productId); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } @GetMapping("/index") public ModelAndView index(@RequestParam(value = "productId", required = false) String productId, Map<String, Object> map) { if (!StringUtils.isEmpty(productId)) { ProductInfo productInfo = productService.findOne(productId); map.put("productInfo", productInfo); } //查询所有的类目 List<ProductCategory> categoryList = categoryService.findAll(); map.put("categoryList", categoryList); return new ModelAndView("product/index", map); } /** * 保存/更新 * @param form * @param bindingResult * @param map * @return */ @PostMapping("/save") // @Cacheable(cacheNames = "product", key = "123") // @Cacheable(cacheNames = "product", key = "456") // @CachePut(cacheNames = "product", key = "123") @CacheEvict(cacheNames = "product", allEntries = true, beforeInvocation = true) public ModelAndView save(@Valid ProductForm form, BindingResult bindingResult, Map<String, Object> map) { if (bindingResult.hasErrors()) { map.put("msg", bindingResult.getFieldError().getDefaultMessage()); map.put("url", "/sell/seller/product/index"); return new ModelAndView("common/error", map); } ProductInfo productInfo = new ProductInfo(); try { //如果productId为空, 说明是新增 if (!StringUtils.isEmpty(form.getProductId())) { productInfo = productService.findOne(form.getProductId()); } else { form.setProductId(KeyUtil.genUniqueKey()); } BeanUtils.copyProperties(form, productInfo); productService.save(productInfo); } catch (SellException e) { map.put("msg", e.getMessage()); map.put("url", "/sell/seller/product/index"); return new ModelAndView("common/error", map); } map.put("url", "/sell/seller/product/list"); return new ModelAndView("common/success", map); } }
使用Mabatis注解方式实现增删改查
mapper层
ProductCategoryMapper.java
package com.imooc.dataobject.mapper; import com.imooc.dataobject.ProductCategory; import org.apache.ibatis.annotations.*; import java.util.List; import java.util.Map; public interface ProductCategoryMapper { @Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{category_type, jdbcType=INTEGER})") int insertByMap(Map<String, Object> map); @Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{categoryType, jdbcType=INTEGER})") int insertByObject(ProductCategory productCategory); @Select("select * from product_category where category_type = #{categoryType}") @Results({ @Result(column = "category_id", property = "categoryId"), @Result(column = "category_name", property = "categoryName"), @Result(column = "category_type", property = "categoryType") }) ProductCategory findByCategoryType(Integer categoryType); @Select("select * from product_category where category_name = #{categoryName}") @Results({ @Result(column = "category_id", property = "categoryId"), @Result(column = "category_name", property = "categoryName"), @Result(column = "category_type", property = "categoryType") }) List<ProductCategory> findByCategoryName(String categoryName); @Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}") int updateByCategoryType(@Param("categoryName") String categoryName, @Param("categoryType") Integer categoryType); @Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}") int updateByObject(ProductCategory productCategory); @Delete("delete from product_category where category_type = #{categoryType}") int deleteByCategoryType(Integer categoryType); ProductCategory selectByCategoryType(Integer categoryType); }
mapper单元测试
ProductCategoryMapperTest.java
package com.imooc.dataobject.mapper; import com.imooc.dataobject.ProductCategory; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductCategoryMapperTest { @Autowired private ProductCategoryMapper mapper; @Test public void insertByMap() throws Exception { Map<String, Object> map = new HashMap<>(); map.put("categoryName", "师兄最不爱"); map.put("category_type", 101); int result = mapper.insertByMap(map); Assert.assertEquals(1, result); } @Test public void insertByObject() { ProductCategory productCategory = new ProductCategory(); productCategory.setCategoryName("师兄最不爱"); productCategory.setCategoryType(102); int result = mapper.insertByObject(productCategory); Assert.assertEquals(1, result); } @Test public void findByCategoryType() { ProductCategory result = mapper.findByCategoryType(102); Assert.assertNotNull(result); } @Test public void findByCategoryName() { List<ProductCategory> result = mapper.findByCategoryName("师兄最不爱"); Assert.assertNotEquals(0, result.size()); } @Test public void updateByCategoryType() { int result = mapper.updateByCategoryType("师兄最不爱的分类", 102); Assert.assertEquals(1, result); } @Test public void updateByObject() { ProductCategory productCategory = new ProductCategory(); productCategory.setCategoryName("师兄最不爱"); productCategory.setCategoryType(102); int result = mapper.updateByObject(productCategory); Assert.assertEquals(1, result); } @Test public void deleteByCategoryType() { int result = mapper.deleteByCategoryType(102); Assert.assertEquals(1, result); } @Test public void selectByCategoryType() { ProductCategory productCategory = mapper.selectByCategoryType(101); Assert.assertNotNull(productCategory); } }
Dao层
ProductCategoryDao.java
package com.imooc.dataobject.dao; import com.imooc.dataobject.mapper.ProductCategoryMapper; import org.springframework.beans.factory.annotation.Autowired; import java.util.Map; public class ProductCategoryDao { @Autowired ProductCategoryMapper mapper; public int insertByMap(Map<String, Object> map) { return mapper.insertByMap(map); } }
对象转换
Form表单对象(Json)转成DTO
OrderForm2OrderDTOConverter.java
package com.imooc.converter; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.imooc.dataobject.OrderDetail; import com.imooc.dto.OrderDTO; import com.imooc.enums.ResultEnum; import com.imooc.exception.SellException; import com.imooc.form.OrderForm; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; @Slf4j public class OrderForm2OrderDTOConverter { public static OrderDTO convert(OrderForm orderForm) { Gson gson = new Gson(); OrderDTO orderDTO = new OrderDTO(); orderDTO.setBuyerName(orderForm.getName()); orderDTO.setBuyerPhone(orderForm.getPhone()); orderDTO.setBuyerAddress(orderForm.getAddress()); orderDTO.setBuyerOpenid(orderForm.getOpenid()); List<OrderDetail> orderDetailList = new ArrayList<>(); try { orderDetailList = gson.fromJson(orderForm.getItems(), new TypeToken<List<OrderDetail>>() { }.getType()); } catch (Exception e) { log.error("【对象转换】错误, string={}", orderForm.getItems()); throw new SellException(ResultEnum.PARAM_ERROR); } orderDTO.setOrderDetailList(orderDetailList); return orderDTO; } }
Data object转DTO
OrderMaster2OrderDTOConverter.java
package com.imooc.converter; import com.imooc.dataobject.OrderMaster; import com.imooc.dto.OrderDTO; import org.springframework.beans.BeanUtils; import java.util.List; import java.util.stream.Collectors; public class OrderMaster2OrderDTOConverter { public static OrderDTO convert(OrderMaster orderMaster) { OrderDTO orderDTO = new OrderDTO(); BeanUtils.copyProperties(orderMaster, orderDTO); return orderDTO; } public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) { return orderMasterList.stream().map(e -> convert(e) ).collect(Collectors.toList()); } }
中文字符乱码
application.properties
spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8
IDEA设置
网页模板
Thymeleaf
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> ... <resources> <resource> <directory>src/main/resources</directory> </resource> </resources>
index.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head lang="en"> <meta charset="UTF-8"/> <title></title> </head> <body> <h1 th:text="${host}">Hello World</h1> </body> </html>
注意:模板的位置可以直接放在src/main/resources/templates/目录下。
application.properties
spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 # Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL. spring.thymeleaf.suffix=.html
ThymeleafTest.java
package com.mindex.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ThymeleafTest { @RequestMapping("/") public String index(ModelMap map) { map.addAttribute("host", "http://www.mindex.com"); return "index"; } }
运行结果如下:
数据库操作
JdbcTemplate
引入POM依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency>
修改配置文件application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=welcome spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Service接口:UserService.java
package com.mindex.service; public interface UserService { void create(String name, Integer age); void deleteByName(String name); Integer getAllUsers(); void deleteAllUsers(); }
Service实现:UserServiceImpl.java
package com.mindex.service.impl; import com.mindex.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; @Override public void create(String name, Integer age) { jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?,?);", name, age); } @Override public void deleteByName(String name) { jdbcTemplate.update("delete from USER where NAME = ?", name); } @Override public Integer getAllUsers() { return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class); } @Override public void deleteAllUsers() { jdbcTemplate.update("delete from USER"); } }
单元测试:UserServiceImplTest.java
package com.mindex.service.impl; import com.mindex.service.UserService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class UserServiceImplTest { @Autowired private UserService userService; @Test public void create() throws Exception { // 插入5个用户 userService.create("a", 1); userService.create("b", 2); userService.create("c", 3); userService.create("d", 4); userService.create("e", 5); Assert.assertEquals(5, userService.getAllUsers().intValue()); } @Test public void deleteByName() throws Exception { userService.deleteByName("b"); userService.deleteByName("c"); Assert.assertEquals(3, userService.getAllUsers().intValue()); } @Test public void getAllUsers() throws Exception { } @Test public void deleteAllUsers() throws Exception { } }
Spring-data-jpa
引入pom依赖:pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
在application.properties
创建数据库连接信息。
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123qweasd spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
spring.jpa.properties.hibernate.hbm2ddl.auto
是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
-
create
:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。 -
create-drop
:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。 -
update
:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。 -
validate
:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
创建实体
创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto
,在应用启动的时候框架会自动去数据库中创建对应的表。
User.java
package com.mindex.entities; import lombok.Data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Data @Entity public class User { @Id @GeneratedValue private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } public User() { } }
创建数据访问接口
下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:UserRepository.java
package com.mindex.repository; import com.mindex.entities.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface UserRepository extends JpaRepository<User, Long> { User findByName(String name); User findByNameAndAge(String name, Integer age); @Query("from User u where u.name=:name") User findUser(@Param("name") String name); }
在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。
下面对上面的UserRepository
做一些解释,该接口继承自JpaRepository
,通过查看JpaRepository
接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。
在我们实际开发中,JpaRepository
接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。
在上例中,我们可以看到下面两个函数:
User findByName(String name)
User findByNameAndAge(String name, Integer age)
它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。
除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。
Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。
单元测试
在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。
package com.mindex.repository; import com.mindex.entities.User; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test public void test() throws Exception { // 创建10条记录 userRepository.save(new User("AAA", 10)); userRepository.save(new User("BBB", 20)); userRepository.save(new User("CCC", 30)); userRepository.save(new User("DDD", 40)); userRepository.save(new User("EEE", 50)); userRepository.save(new User("FFF", 60)); userRepository.save(new User("GGG", 70)); userRepository.save(new User("HHH", 80)); userRepository.save(new User("III", 90)); userRepository.save(new User("JJJ", 100)); // 测试findAll, 查询所有记录 Assert.assertEquals(10, userRepository.findAll().size()); // 测试findByName, 查询姓名为FFF的User Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue()); // 测试findUser, 查询姓名为FFF的User Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue()); // 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName()); // 测试删除姓名为AAA的User userRepository.delete(userRepository.findByName("AAA")); // 测试findAll, 查询所有记录, 验证上面的删除是否成功 Assert.assertEquals(9, userRepository.findAll().size()); } }
集成Redis
手动配置集成Redis
引入POM依赖:pom.xml
<!--此处并不是直接使用spring提供的redis-starter--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
外部配置文件:application.yaml
jedis: host: 127.0.0.1 port: 6379 pool: max-idle: 300 min-idle: 10 max-total: 600 max-wait: 1000 block-when-exhausted: true
Java配置类(替代传统xml):RedisConfig.java
package com.mindex.config; import lombok.Data; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Data @Component public class RedisConfig { @Bean("jedis.config") public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle, @Value("${jedis.pool.max-idle}") int maxIdle, @Value("${jedis.pool.max-wait}") int maxWaitMillis, @Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted, @Value("${jedis.pool.max-total}") int maxTotal) { JedisPoolConfig config = new JedisPoolConfig(); config.setMinIdle(minIdle); config.setMaxIdle(maxIdle); config.setMaxWaitMillis(maxWaitMillis); config.setMaxTotal(maxTotal); // 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true config.setBlockWhenExhausted(blockWhenExhausted); // 是否启用pool的jmx管理功能, 默认true config.setJmxEnabled(true); return config; } @Bean public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config, @Value("${jedis.host}") String host, @Value("${jedis.port}") int port) { return new JedisPool(config, host, port); } }
Service接口定义:RedisService.java
package com.mindex.service; public interface RedisService { String get(String key); boolean set(String key, String val); }
Service接口实现类:RedisServiceImpl.java
package com.mindex.service.impl; import com.mindex.service.RedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @Service public class RedisServiceImpl implements RedisService { // 此处直接注入即可 @Autowired private JedisPool jedisPool; @Override public String get(String key) { Jedis jedis = this.jedisPool.getResource(); String ret; try { ret = jedis.get(key); } finally { if (jedis != null) jedis.close(); } return ret; } @Override public boolean set(String key, String val) { Jedis jedis = this.jedisPool.getResource(); try { return "OK".equals(jedis.set(key, val)); } finally { if (jedis != null) jedis.close(); } } }
测试:RedisServiceImplTest.java
package com.mindex.service.impl; import com.mindex.service.RedisService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class RedisServiceImplTest { @Autowired private RedisService redisService; @Test public void testGet() { // test set boolean status = this.redisService.set("foo", "bar"); Assert.assertTrue(status); // test get String str = this.redisService.get("foo"); Assert.assertEquals("bar", str); } }
在Redis中检查结果
使用spring-boot-starter-data-redis
外部配置文件:application.yaml
jedis: host: 127.0.0.1 port: 6379 pool: max-idle: 300 min-idle: 10 max-total: 600 max-wait: 1000 block-when-exhausted: true
配置类:RedisConfig1.java
package com.mindex.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig1 { @Autowired private RedisConnectionFactory redisConnectionFactory; /** * 实例化 RedisTemplate 对象 * */ @Bean public RedisTemplate<String, Object> functionDomainRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); this.initRedisTemplate(redisTemplate, redisConnectionFactory); return redisTemplate; } /** * 序列化设置 */ private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setConnectionFactory(factory); } @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } @Bean public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForValue(); } @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } }
简单测试:RedisAnotherConfigTest.java
package com.mindex; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @RunWith(SpringRunner.class) @SpringBootTest public class RedisAnotherConfigTest { @Autowired private ValueOperations<String, Object> valueOperations; @Autowired private RedisTemplate<String, Object> redisTemplate; @Test public void contextLoads() { } @Test public void testStringOps() { this.valueOperations.set("k1", "spring-redis"); Boolean hasKey = this.redisTemplate.hasKey("k1"); assertEquals(true, hasKey); Object str = this.valueOperations.get("k1"); assertNotNull(str); assertEquals("spring-redis", str.toString()); } }
邮件
使用JavaMailSender发送邮件
引入POM依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
增加配置:application.properties
注意:这里的
password
是邮箱授权码,不是邮箱密码。
spring.mail.host=smtp.qq.com spring.mail.username=85648606@qq.com spring.mail.password=clqpsraiifwqbidg spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true
单元测试:MailTest.java
package com.mindex; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class MailTest { @Autowired private JavaMailSender mailSender; @Test public void sendSimpleMail() throws Exception { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom("85648606@qq.com"); message.setTo("wfgdlut@msn.com"); message.setSubject("主题:简单邮件"); message.setText("测试邮件内容"); mailSender.send(message); } }
发送html格式邮件
@Test public void sendAttachmentsMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }
发送包含内嵌图片的邮件
/** * 发送包含内嵌图片的邮件 * * @throws Exception */ @Test public void sendAttachedImageMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>"); // cid为固定写法,imageId指定一个标识 sb.append("<img src=\"cid:imageId\"/></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置imageId FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png")); mimeMessageHelper.addInline("imageId", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }
发送包含附件的邮件
/** * 发送包含附件的邮件 * @throws Exception */ @Test public void sendAttendedFileMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8"); mimeMessageHelper.setFrom("85648606@qq.com"); mimeMessageHelper.setTo("wfgdlut@msn.com"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置附件 FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png")); mimeMessageHelper.addAttachment("test.png", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C# Windows服务以指定用户运行
参考一下 https://bbs.csdn.net/topics/330151879 服务程序以LocalSystem安装运行没问题,但用这个账户运行的服务无法访问局域网共享资源,比较麻烦,所以想指定用某个账户来启动服务。但是当我指定用Administrators组里某个用户安装服务时,显示“帐户名无效或不存在,或者密码对于指定的帐户名无效”的信息,导致服务安装不成功。其实账户名和密码当然是正确的。 弹出窗口输入 .\用户名 和 密码 注意: .\
- 下一篇
自动启动 Windows 10 UWP 应用
原文: https://docs.microsoft.com/zh-cn/windows/uwp/xbox-apps/automate-launching-uwp-apps 简介 开发人员有多种选项可用于实现自动启动通用 Windows 平台 (UWP) 应用。 在本文中,我们将探讨通过使用协议激活和启动激活来启动应用的方法。 协议激活允许应用根据给定协议将自身注册为处理程序。 启动激活是正常的应用启动,例如从应用磁贴启动。 通过每个激活方法,你可以选择使用命令行或启动器应用程序。 对于所有的激活方法,如果应用当前正在运行,激活会将应用显示到前台(这将重新激活它)并提供新的激活参数。 这允许灵活使用激活命令向应用提供新消息。 请务必注意,需要针对激活方法编译和部署项目才能运行新更新的应用。 协议激活 按照以下步骤来设置适用于应用的协议激活: 在 Visual Studio 中打开Package.appxmanifest文件。 选择“声明”****选项卡。 在“可用声明”*下拉列表中,选择“协议”,然后选择“添加”***。 在“属性”*下的“名称”*字段中,输入唯一名称以启动应用。 保存...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- 2048小游戏-低调大师作品
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程