lijj 2 vuotta sitten
vanhempi
commit
a1314f1811
33 muutettua tiedostoa jossa 2323 lisäystä ja 23 poistoa
  1. 25 0
      admin/src/main/java/org/springblade/modules/member/constant/MemberTokenConstant.java
  2. 97 0
      admin/src/main/java/org/springblade/modules/member/controller/pc/MemberAccountController.java
  3. 230 0
      admin/src/main/java/org/springblade/modules/member/controller/pc/MemberInfoController.java
  4. 148 0
      admin/src/main/java/org/springblade/modules/member/controller/wx/WXLoginController.java
  5. 64 0
      admin/src/main/java/org/springblade/modules/member/controller/wx/WXMemberInfoController.java
  6. 18 0
      admin/src/main/java/org/springblade/modules/member/dto/MemberAccountDTO.java
  7. 17 0
      admin/src/main/java/org/springblade/modules/member/dto/MemberDTO.java
  8. 47 0
      admin/src/main/java/org/springblade/modules/member/dto/MemberStatisticInfo.java
  9. 65 0
      admin/src/main/java/org/springblade/modules/member/entity/MemberAccountEntity.java
  10. 204 0
      admin/src/main/java/org/springblade/modules/member/entity/MemberEntity.java
  11. 40 0
      admin/src/main/java/org/springblade/modules/member/enums/AccountTypeEnum.java
  12. 37 0
      admin/src/main/java/org/springblade/modules/member/enums/DateTypeEnum.java
  13. 32 0
      admin/src/main/java/org/springblade/modules/member/enums/MemberAuditStatusEnum.java
  14. 24 0
      admin/src/main/java/org/springblade/modules/member/enums/SexEnum.java
  15. 13 0
      admin/src/main/java/org/springblade/modules/member/mapper/MemberAccountMapper.java
  16. 10 23
      admin/src/main/java/org/springblade/modules/ycwh/mapper/AccountMapper.xml
  17. 26 0
      admin/src/main/java/org/springblade/modules/member/mapper/MemberMapper.java
  18. 68 0
      admin/src/main/java/org/springblade/modules/member/mapper/MemberMapper.xml
  19. 10 0
      admin/src/main/java/org/springblade/modules/member/mapper/member_account.sql
  20. 10 0
      admin/src/main/java/org/springblade/modules/member/mapper/member_info.sql
  21. 30 0
      admin/src/main/java/org/springblade/modules/member/service/IMemberAccountService.java
  22. 72 0
      admin/src/main/java/org/springblade/modules/member/service/IMemberInfoService.java
  23. 199 0
      admin/src/main/java/org/springblade/modules/member/service/impl/MemberAccountServiceImpl.java
  24. 191 0
      admin/src/main/java/org/springblade/modules/member/service/impl/MemberInfoServiceImpl.java
  25. 116 0
      admin/src/main/java/org/springblade/modules/member/util/DateUtil.java
  26. 148 0
      admin/src/main/java/org/springblade/modules/member/util/WXTokenUtil.java
  27. 18 0
      admin/src/main/java/org/springblade/modules/member/vo/MemberAccountVO.java
  28. 31 0
      admin/src/main/java/org/springblade/modules/member/vo/MemberVO.java
  29. 31 0
      admin/src/main/java/org/springblade/modules/member/wrapper/MemberAccountWrapper.java
  30. 48 0
      admin/src/main/java/org/springblade/modules/member/wrapper/MemberInfoWrapper.java
  31. 147 0
      admin/src/main/java/org/springblade/modules/member/wx/config/WxMaConfiguration.java
  32. 45 0
      admin/src/main/java/org/springblade/modules/member/wx/config/WxMaProperties.java
  33. 62 0
      admin/src/main/java/org/springblade/modules/member/wx/dto/LoginTokenDTO.java

+ 25 - 0
admin/src/main/java/org/springblade/modules/member/constant/MemberTokenConstant.java

@@ -0,0 +1,25 @@
+package org.springblade.modules.member.constant;
+
+public interface
+MemberTokenConstant {
+	String AVATAR = "avatar";
+	String BEARER = "bearer";
+	String ACCESS_TOKEN = "access_token";
+	String REFRESH_TOKEN = "refresh_token";
+	String TOKEN_TYPE = "token_type";
+	String EXPIRES_IN = "expires_in";
+	String ACCOUNT = "account";
+	String MEMBER_NAME = "member_name";
+	String NICK_NAME = "nick_name";
+	String REAL_NAME = "real_name";
+	String MEMBER_ID = "member_id";
+	String SHOP_ID = "shop_id";
+	String SHOP_NAME = "shop_name";
+	String TENANT_ID = "tenant_id";
+	String CLIENT_ID = "client_id";
+	String DETAIL = "detail";
+	String LICENSE = "license";
+	String LICENSE_NAME = "powered by Farwalker";
+	String DEFAULT_AVATAR = "";
+	Integer AUTH_LENGTH = 7;
+}

+ 97 - 0
admin/src/main/java/org/springblade/modules/member/controller/pc/MemberAccountController.java

@@ -0,0 +1,97 @@
+package org.springblade.modules.member.controller.pc;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+import org.springblade.modules.member.service.IMemberAccountService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+
+/**
+ *  控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/member_account")
+@Api(value = "", tags = "")
+public class MemberAccountController extends BladeController {
+
+	private final IMemberAccountService memberAccountService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "传入member_account")
+	public R<MemberAccountEntity> detail(MemberAccountEntity member_account) {
+		MemberAccountEntity detail = memberAccountService.getOne(Condition.getQueryWrapper(member_account));
+		return R.data(detail);
+	}
+
+	/**
+	 * 分页 代码自定义代号
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@ApiOperation(value = "分页", notes = "传入member_account")
+	public R<IPage<MemberAccountEntity>> list(MemberAccountEntity member_account, Query query) {
+		IPage<MemberAccountEntity> pages = memberAccountService.page(Condition.getPage(query), Condition.getQueryWrapper(member_account));
+		return R.data(pages);
+	}
+
+	/**
+	 * 新增 代码自定义代号
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@ApiOperation(value = "新增", notes = "传入member_account")
+	public R save(@Valid @RequestBody MemberAccountEntity member_account) {
+		return R.status(memberAccountService.save(member_account));
+	}
+
+	/**
+	 * 修改 代码自定义代号
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@ApiOperation(value = "修改", notes = "传入member_account")
+	public R update(@Valid @RequestBody MemberAccountEntity member_account) {
+		return R.status(memberAccountService.updateById(member_account));
+	}
+
+	/**
+	 * 新增或修改 代码自定义代号
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "新增或修改", notes = "传入member_account")
+	public R submit(@Valid @RequestBody MemberAccountEntity member_account) {
+		return R.status(memberAccountService.saveOrUpdate(member_account));
+	}
+
+
+	/**
+	 * 删除 代码自定义代号
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@ApiOperation(value = "逻辑删除", notes = "传入ids")
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(memberAccountService.deleteLogic(Func.toLongList(ids)));
+	}
+
+}

+ 230 - 0
admin/src/main/java/org/springblade/modules/member/controller/pc/MemberInfoController.java

@@ -0,0 +1,230 @@
+package org.springblade.modules.member.controller.pc;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.member.dto.MemberDTO;
+import org.springblade.modules.member.dto.MemberStatisticInfo;
+import org.springblade.modules.member.entity.MemberEntity;
+import org.springblade.modules.member.service.IMemberInfoService;
+import org.springblade.modules.member.vo.MemberVO;
+import org.springblade.modules.member.wrapper.MemberInfoWrapper;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/member_info")
+@Api(value = "会员信息管理模块", tags = "会员信息管理模块")
+public class MemberInfoController extends BladeController {
+
+	private final IMemberInfoService memberInfoService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/detail")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "传入member_info")
+	public R<MemberVO> detail(MemberEntity member_info) {
+		MemberEntity detail = memberInfoService.getOne(Condition.getQueryWrapper(member_info));
+		if (Func.isNotEmpty(detail)) {
+			return R.data(MemberInfoWrapper.build().entityVO(detail));
+		} else {
+			return R.data(null);
+		}
+	}
+
+	/**
+	 * 分页 代码自定义代号
+	 */
+	@GetMapping("/list")
+	@ApiOperationSupport(order = 2)
+	@ApiOperation(value = "分页", notes = "传入member_info")
+	public R<IPage<MemberVO>> list(@RequestParam Map<String, Object> MemberInfo, Query query) {
+		String shopId = (String) MemberInfo.get("shopId");//门店
+		String cardStatus = (String) MemberInfo.get("cardStatus");//卡状态
+		String cardType = (String) MemberInfo.get("cardType");//卡类型
+		String memberInfo = (String) MemberInfo.get("memberInfo");//会员卡号/微信号/姓名/手机号/标签
+		MemberInfo.remove("shopId");
+		MemberInfo.remove("cardStatus");
+		MemberInfo.remove("cardType");
+		MemberInfo.remove("memberInfo");
+
+		QueryWrapper<MemberEntity> wrapper = Condition.getQueryWrapper(MemberInfo, MemberEntity.class);
+		if (Func.isNotEmpty(memberInfo)) {
+			wrapper.like("real_name", memberInfo).or().like("member_code", memberInfo).or().like("phone", memberInfo);
+		}
+		if (Func.isNotEmpty(shopId)) {
+			wrapper.eq("filing_shop_id", shopId);
+		}
+
+		IPage<MemberEntity> pages = memberInfoService.page(Condition.getPage(query), wrapper);
+		return R.data(MemberInfoWrapper.build().pageVO(pages));
+	}
+
+	/**
+	 * 新增 代码自定义代号
+	 */
+	@PostMapping("/save")
+	@ApiOperationSupport(order = 4)
+	@ApiOperation(value = "新增", notes = "传入member_info")
+	public R save(@Valid @RequestBody MemberEntity member_info) {
+		return R.status(memberInfoService.save(member_info));
+	}
+
+	/**
+	 * 修改 代码自定义代号
+	 */
+	@PostMapping("/update")
+	@ApiOperationSupport(order = 5)
+	@ApiOperation(value = "修改", notes = "传入member_info")
+	public R update(@Valid @RequestBody MemberEntity member_info) {
+		if (Func.isEmpty(member_info.getRealName())) {
+			return R.fail("会员姓名不能为空!");
+		}
+		if (Func.isEmpty(member_info.getPhone())) {
+			return R.fail("联系方式不能为空!");
+		}
+		if (Func.isEmpty(member_info.getMemberCode())) {
+			return R.fail("会员卡号不能为空!");
+		}
+
+		return R.status(memberInfoService.updateById(member_info));
+	}
+
+	/**
+	 * 新增或修改 代码自定义代号
+	 */
+	@PostMapping("/submit")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "新增或修改", notes = "传入member_info")
+	public R submit(@Valid @RequestBody MemberDTO memberInfoDTO) {
+		if (Func.isEmpty(memberInfoDTO.getRealName())) {
+			return R.fail("会员姓名不能为空!");
+		}
+		if (Func.isEmpty(memberInfoDTO.getPhone())) {
+			return R.fail("联系方式不能为空!");
+		}
+		if (Func.isEmpty(memberInfoDTO.getMemberCode())) {
+			return R.fail("会员卡号不能为空!");
+		}
+
+		//新增的时候判断,电话是否已存在
+		if (Func.isEmpty(memberInfoDTO.getId())) {//id为空则为新增
+			Boolean memPhone = memberInfoService.getMemPhone(memberInfoDTO.getPhone());
+			if (memPhone) {
+				return R.fail("新增失败!该手机号已存在!");
+			}
+		}
+
+		MemberEntity memberInfoEntity = memberInfoService.setMemberInfoEntity(memberInfoDTO);
+
+		return R.status(memberInfoService.saveOrUpdate(memberInfoEntity));
+	}
+
+
+	/**
+	 * 删除 代码自定义代号
+	 */
+	@PostMapping("/remove")
+	@ApiOperationSupport(order = 7)
+	@ApiOperation(value = "逻辑删除", notes = "传入ids")
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(memberInfoService.deleteLogic(Func.toLongList(ids)));
+	}
+
+	/**
+	 * 根据会员卡号获取会员信息
+	 *
+	 * @param memberCode
+	 * @return
+	 */
+	@GetMapping("/get_mem_info_by_code")
+	@ApiOperationSupport(order = 8)
+	@ApiOperation(value = "根据会员卡号获取会员信息", notes = "")
+	public R getMemberInfo(@ApiParam(value = "会员卡号", required = true) @RequestParam String memberCode) {
+		return R.data(MemberInfoWrapper.build().entityVO(memberInfoService.getOneByMemberCode(memberCode)));
+	}
+
+	/**
+	 * 统计会员信息
+	 *
+	 * @return
+	 */
+	@GetMapping("/statistic_info")
+	@ApiOperationSupport(order = 9)
+	@ApiOperation(value = "统计会员信息", notes = "")
+	public R getStatisticMemInfo() {
+
+		MemberStatisticInfo memberStatisticInfo = new MemberStatisticInfo();
+
+		//今日会员生日人数
+		List<MemberEntity> today = memberInfoService.getBirMemByDate("today");
+		memberStatisticInfo.setBirTodayMemNum(today.size());
+
+		//本月会员生日人数
+		List<MemberEntity> month = memberInfoService.getBirMemByDate("month");
+		memberStatisticInfo.setBirMonthMemNum(month.size());
+
+		//本月还有的会员生日人数(剩余的)monthSurplus
+		List<MemberEntity> monthSurplus = memberInfoService.getBirMemByDate("monthSurplus");
+		memberStatisticInfo.setBirMonthSurplusMemNum(monthSurplus.size());
+
+		//平均每日增长多少位会员
+		memberStatisticInfo.setAverageDailyIncrease(memberInfoService.averageDailyIncrease(null));
+
+		return R.data(memberStatisticInfo);
+	}
+
+	/**
+	 * 根据时间段获取会员信息
+	 *
+	 * @param dateType 今日(today),近7天(rec7day),本月(month)
+	 * @return
+	 */
+	@GetMapping("get_mem_info_by_date")
+	@ApiOperationSupport(order = 10)
+	@ApiOperation(value = "根据时间段获取会员信息", notes = "")
+	public R getMemInfoByDate(String dateType) {
+		List<MemberEntity> birMemByDate = memberInfoService.getBirMemByDate(dateType);
+		return R.data(MemberInfoWrapper.build().listVO(birMemByDate));
+	}
+
+	/**
+	 * 根据会员卡号/手机号/微信卡号获取推荐人
+	 *
+	 * @param phone
+	 * @return
+	 */
+	@GetMapping("referral_by_phone")
+	@ApiOperationSupport(order = 11)
+	@ApiOperation(value = "根据会员卡号/手机号/微信卡号获取推荐人", notes = "")
+	public R getReferralByPhone(String phone) {
+
+		QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>();
+		wrapper.eq("phone", phone).or().eq("member_code", phone);//会员卡号/手机号
+		wrapper.last("limit 1");
+		MemberVO memberInfo = MemberInfoWrapper.build().entityVO(memberInfoService.getOne(wrapper));
+		return R.data(memberInfo);
+	}
+
+}

+ 148 - 0
admin/src/main/java/org/springblade/modules/member/controller/wx/WXLoginController.java

@@ -0,0 +1,148 @@
+package org.springblade.modules.member.controller.wx;
+
+
+import com.github.xiaoymin.knife4j.annotations.ApiSort;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springblade.core.jwt.JwtUtil;
+import org.springblade.core.jwt.props.JwtProperties;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.log.annotation.ApiLog;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.secure.BladeUser;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.core.tool.utils.WebUtil;
+import org.springblade.modules.member.service.IMemberAccountService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 登录注册
+ *
+ * @author Chensl
+ */
+@NonDS
+@ApiSort(1)
+@RestController
+@AllArgsConstructor
+@RequestMapping("yuecai-wx/member/login")
+@Api(value = "登录注册", tags = "微信相关登录注册")
+public class WXLoginController {
+
+	private final BladeRedis bladeRedis;
+	private final JwtProperties jwtProperties;
+	private final IMemberAccountService accountService;
+
+
+
+	/**
+	 * 微信小程序登录
+	 * @param clientCode
+	 * @param code
+	 * @return
+	 */
+	@ApiLog("微信小程序登录")
+	@PostMapping("/miniapp")
+	@ApiOperation(value = "微信小程序登录", notes =
+			"1、请求头暂为固定数据:Authorization - Basic c2FiZXI6c2FiZXJfc2VjcmV0" +
+		 	"2、如无注册则先注册" +
+			"3、不获取用户详细信息,静默登录" +
+			"4、无返回则是授权报错,需重新授权登录")
+	public R<Kv> wxminiappLogin(@Valid @RequestParam String clientCode, @Valid @RequestParam String code, @RequestHeader("Tenant-Id")String tenantId) {
+		return R.data(accountService.doWxMiniappLogin(clientCode, code, tenantId));
+	}
+
+	/**
+	 * 微信小程序刷新token
+	 * @param refreshToken
+	 * @return
+	 */
+	@ApiLog("微信小程序刷新token")
+	@PostMapping("/refresh")
+	@ApiOperation(value = "微信小程序刷新token", notes =
+			"1、请求头暂为固定数据:Authorization - Basic c2FiZXI6c2FiZXJfc2VjcmV0" +
+					"2、如无注册则先注册" +
+					"3、不获取用户详细信息,静默登录" +
+					"4、无返回则是授权报错,需重新授权登录")
+	public R<Kv> refresh( @Valid @RequestParam String refreshToken, @RequestHeader("Tenant-Id")String tenantId) {
+		return R.data(accountService.doWxMiniappLogin("clientCode", refreshToken, tenantId));
+	}
+
+	public R<Kv> wxminiappRegister(@Valid @RequestParam String clientCode, @Valid @RequestParam String code){
+		return R.data(accountService.doWxMiniappRegister(clientCode, code));
+	}
+
+
+//	/**
+//	 * 微信小程序-会员注册
+//	 *
+//	 * @param wxcode    微信登录返回的 code
+//	 * @param encrypted 消息密文
+//	 * @param iv        加密算法的初始向量
+//	 */
+//	@ApiLog("微信小程序-会员注册")
+//	@PostMapping("/register")
+//	@ApiOperation(value = "微信小程序-会员注册", notes = "")
+//	public Kv doRegister(@ApiParam(value = "门店id", required = true) @RequestParam Long shopId,
+//						 @ApiParam(value = "微信编码", required = true) @RequestParam String wxcode,
+//						 @ApiParam(value = "消息密文", required = true) @RequestParam String encrypted,
+//						 @ApiParam(value = "加密算法的初始向量", required = true) @RequestParam String iv) {
+//		Kv authInfo = Kv.create();
+//		if (Func.isBlank(wxcode) || Func.isBlank(encrypted) || Func.isBlank(iv)) {
+//			return authInfo.set("code", HttpServletResponse.SC_BAD_REQUEST).set("msg", "微信编码/消息密文/加密算法的初始向量不能为空").set("success", Boolean.FALSE);
+//		}
+//		WxMaUserInfo userInfo;
+//		try {
+//			String appid = properties.getConfigs().get(0).getAppid();
+//			final WxMaService wxService = WxMaConfiguration.getMaService(appid);
+//
+//			WxMaUserService mauserBiz = wxService.getUserService();
+//			WxMaJscode2SessionResult session = mauserBiz.getSessionInfo(wxcode);
+//			// 解密用户信息
+//			userInfo = mauserBiz.getUserInfo(session.getSessionKey(), encrypted, iv);
+//			if (Func.isEmpty(userInfo)) {
+//				return authInfo.set("code", HttpServletResponse.SC_BAD_REQUEST).set("msg", "登录失败,请重新登录").set("success", Boolean.FALSE);
+//			}
+//		} catch (Exception e) {
+//			return authInfo.set("code", HttpServletResponse.SC_BAD_REQUEST).set("msg", "登录失败,请重新登录").set("success", Boolean.FALSE);
+//		}
+//		try {
+//			// 校验用户是否已经注册过
+//			LoginTokenDTO loginToken = accountService.register(AccountTypeEnum.WX_MINI_APP.getCode(), userInfo.getOpenId(), userInfo.getUnionId(),
+//				null, userInfo.getAvatarUrl(), userInfo.getNickName());
+//			return TokenUtil.createAuthInfo(loginToken);
+//		} catch (Exception e) {
+//			return authInfo.set("code", HttpServletResponse.SC_BAD_REQUEST).set("msg", "登录失败,请重新登录").set("success", Boolean.FALSE);
+//		}
+//		return null;
+//	}
+
+	@GetMapping("/oauth/logout")
+	@ApiOperation(value = "退出登录")
+	public Kv logout() {
+		BladeUser user = AuthUtil.getUser();
+		if (user != null && jwtProperties.getState()) {
+			String token = JwtUtil.getToken(WebUtil.getRequest().getHeader(TokenConstant.HEADER));
+			JwtUtil.removeAccessToken(user.getTenantId(), String.valueOf(user.getUserId()), token);
+		}
+
+		// 移除存储的redis数据,用于实时统计在线人数
+		String ONLINE_LOGIN = "online:login:";
+
+		List<Object> list = bladeRedis.mGet(ONLINE_LOGIN);
+		String cache = (String) this.bladeRedis.get(ONLINE_LOGIN + user.getAccount());
+		if (StringUtil.isNotBlank(user.getAccount()) && StringUtil.equalsIgnoreCase(cache, user.getAccount())) {
+			this.bladeRedis.del(ONLINE_LOGIN + user.getAccount());
+		}
+		return Kv.create().set("code", "200").set("msg", "success").set("success", Boolean.TRUE);
+	}
+
+
+}

+ 64 - 0
admin/src/main/java/org/springblade/modules/member/controller/wx/WXMemberInfoController.java

@@ -0,0 +1,64 @@
+package org.springblade.modules.member.controller.wx;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.modules.member.entity.MemberEntity;
+import org.springblade.modules.member.service.IMemberInfoService;
+import org.springframework.web.bind.annotation.*;
+
+
+/**
+ * 控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/yuecai-wx/member")
+@Api(value = "会员信息管理模块", tags = "会员信息管理模块")
+public class WXMemberInfoController extends BladeController {
+
+	private final IMemberInfoService memberInfoService;
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/random/name")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "")
+	public R<String> randomName() {
+		return R.data(memberInfoService.getRandomName());
+	}
+
+	/**
+	 * 详情
+	 */
+	@GetMapping("/random/avatar")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "")
+	public R<String> randomAvatar() {
+		return R.data(memberInfoService.getRandomAvatar());
+	}
+
+	/**
+	 * 详情
+	 */
+	@PostMapping("/random/save")
+	@ApiOperationSupport(order = 1)
+	@ApiOperation(value = "详情", notes = "")
+	public R<Boolean> saveNicknameAndAvatar(@RequestParam String name, @RequestParam String avatar) {
+		Long memberId = AuthUtil.getUserId();
+		Boolean status = memberInfoService.update(Wrappers.<MemberEntity>lambdaUpdate()
+			.eq(MemberEntity::getId, memberId)
+			.set(MemberEntity::getNickName, name)
+			.set(MemberEntity::getAvator, avatar));
+		return R.data(status);
+	}
+
+}

+ 18 - 0
admin/src/main/java/org/springblade/modules/member/dto/MemberAccountDTO.java

@@ -0,0 +1,18 @@
+package org.springblade.modules.member.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+
+/**
+ *  模型DTO
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemberAccountDTO extends MemberAccountEntity {
+
+	private static final long serialVersionUID = 1L;
+
+}

+ 17 - 0
admin/src/main/java/org/springblade/modules/member/dto/MemberDTO.java

@@ -0,0 +1,17 @@
+package org.springblade.modules.member.dto;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.member.entity.MemberEntity;
+
+/**
+ *  模型DTO
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemberDTO extends MemberEntity {
+
+	private static final long serialVersionUID = 1L;
+
+}

+ 47 - 0
admin/src/main/java/org/springblade/modules/member/dto/MemberStatisticInfo.java

@@ -0,0 +1,47 @@
+package org.springblade.modules.member.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class MemberStatisticInfo implements Serializable {
+
+	/**
+	 * 今日过生日的会员人数
+	 */
+	@ApiModelProperty("今日过生日的会员人数")
+	private Integer birTodayMemNum;
+
+	/**
+	 * 本月过生日的会员人数
+	 */
+	@ApiModelProperty("本月过生日的会员人数")
+	private Integer birMonthMemNum;
+
+	/**
+	 * 本月还有的会员生日人数(剩余的)
+	 */
+	@ApiModelProperty("本月还有的会员生日人数(剩余的)")
+	private Integer birMonthSurplusMemNum;
+
+	/**
+	 * 会员总数
+	 */
+	@ApiModelProperty("会员总数")
+	private Integer allMemNum;
+
+	/**
+	 * 储值卡总额
+	 */
+	@ApiModelProperty("储值卡总额")
+	private BigDecimal totalAdvances;
+
+	/**
+	 * 平均每日增长多少位会员
+	 */
+	@ApiModelProperty("平均每日增长多少位会员")
+	private BigDecimal averageDailyIncrease;
+}

+ 65 - 0
admin/src/main/java/org/springblade/modules/member/entity/MemberAccountEntity.java

@@ -0,0 +1,65 @@
+package org.springblade.modules.member.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+
+
+/**
+ *  实体类
+ *
+ * @author Chill
+ */
+@Data
+@TableName("ycwh_member_account")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "MemberAccount对象", description = "")
+public class MemberAccountEntity extends TenantEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 登录账号
+	 */
+	@ApiModelProperty(value = "登录账号")
+	private String account;
+	/**
+	 * 附加账号(unionId)
+	 */
+	@ApiModelProperty(value = "附加账号(unionId)")
+	private String appendAccount;
+	/**
+	 * 账户类型(账号、手机、QQ、微信等)
+	 */
+	@ApiModelProperty(value = "账户类型(账号、手机、QQ、微信等)")
+	private String accountType;
+	/**
+	 * 登录密码
+	 */
+	@ApiModelProperty(value = "登录密码")
+	private String password;
+	/**
+	 * 密码盐
+	 */
+	@ApiModelProperty(value = "密码盐")
+	private String salt;
+	/**
+	 * 启用状态
+	 */
+	@ApiModelProperty(value = "启用状态")
+	private Boolean isEnabled;
+	/**
+	 * 会员ID
+	 */
+	@ApiModelProperty(value = "会员ID")
+	private Long memberId;
+	/**
+	 * 顺序
+	 */
+	@ApiModelProperty(value = "顺序")
+	private Integer sort;
+
+}

+ 204 - 0
admin/src/main/java/org/springblade/modules/member/entity/MemberEntity.java

@@ -0,0 +1,204 @@
+package org.springblade.modules.member.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+
+/**
+ *  实体类
+ *
+ * @author Chill
+ */
+@Data
+@TableName("ycwh_member")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "Member对象", description = "")
+public class MemberEntity extends TenantEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 建档门店id
+	 */
+	@ApiModelProperty(value = "来源产品编号")
+	private String fromProductCode;
+
+	/**
+	 * 账号名称
+	 */
+//	@ApiModelProperty(value = "账号名称")
+//	private String name;
+	/**
+	 * 密码
+	 */
+//	@ApiModelProperty(value = "密码")
+//	private String password;
+	/**
+	 * 支付密码
+	 */
+	@ApiModelProperty(value = "支付密码")
+	private String payPassword;
+	/**
+	 * 注册IP
+	 */
+	@ApiModelProperty(value = "注册IP")
+	private String regIp;
+	/**
+	 * 注册时间
+	 */
+	@ApiModelProperty(value = "注册时间")
+	private Date registerTime;
+	/**
+	 * 审核状态
+	 */
+	@ApiModelProperty(value = "审核状态")
+	private String auditStatus;
+	/**
+	 * 所属地区
+	 */
+	@ApiModelProperty(value = "所属地区")
+	private String areaCode;
+	/**
+	 * 现地址
+	 */
+	@ApiModelProperty(value = "现地址")
+	private String address;
+	/**
+	 * 户口所在地
+	 */
+	@ApiModelProperty(value = "户口所在地")
+	private String householdAddress;
+	/**
+	 * 手机
+	 */
+	@ApiModelProperty(value = "手机")
+	private String phone;
+	/**
+	 * 固定电话
+	 */
+	@ApiModelProperty(value = "固定电话")
+	private String tel;
+	/**
+	 * 会员级别ID
+	 */
+	@ApiModelProperty(value = "会员级别ID")
+	private Long levelId;
+	/**
+	 * 性别
+	 */
+	@ApiModelProperty(value = "性别")
+	private String sex;
+	/**
+	 * 出生年月
+	 */
+	@ApiModelProperty(value = "出生年月")
+	private Date birthday;
+	/**
+	 * 邮箱
+	 */
+	@ApiModelProperty(value = "邮箱")
+	private String email;
+	/**
+	 * 昵称
+	 */
+	@ApiModelProperty(value = "昵称")
+	private String nickName;
+	/**
+	 * 真实姓名
+	 */
+	@ApiModelProperty(value = "真实姓名")
+	private String realName;
+	/**
+	 * 会员卡号
+	 */
+	@ApiModelProperty(value = "会员卡号")
+	private String memberCode;
+	/**
+	 * 喜好的语言
+	 */
+	@ApiModelProperty(value = "喜好的语言")
+	private String favLang;
+	/**
+	 * 喜好的货币
+	 */
+	@ApiModelProperty(value = "喜好的货币")
+	private String favCurrency;
+	/**
+	 * 登录次数
+	 */
+	@ApiModelProperty(value = "登录次数")
+	private Integer loginCount;
+	/**
+	 * 消费次数
+	 */
+	@ApiModelProperty(value = "消费次数")
+	private Integer consumeCount;
+	/**
+	 * 商城下单次数
+	 */
+	@ApiModelProperty(value = "商城下单次数")
+	private Integer orderCount;
+	/**
+	 * 头像
+	 */
+	@ApiModelProperty(value = "头像")
+	private String avator;
+	/**
+	 * 二维码
+	 */
+	@ApiModelProperty(value = "二维码")
+	private String qrcode;
+	/**
+	 * 积分
+	 */
+	@ApiModelProperty(value = "积分")
+	private Integer point;
+	/**
+	 * 预存款
+	 */
+	@ApiModelProperty(value = "预存款")
+	private BigDecimal deposit;
+	/**
+	 * 账户余额
+	 */
+	@ApiModelProperty(value = "账户余额")
+	private BigDecimal advances;
+	/**
+	 * 推荐码
+	 */
+	@ApiModelProperty(value = "推荐码")
+	private String referralCode;
+
+	/**
+	 * 距离下一级积分
+	 */
+	@ApiModelProperty(value = "距离下一级积分")
+	private Integer needPoint;
+	/**
+	 * 顺序
+	 */
+	@ApiModelProperty(value = "顺序")
+	private Integer sort;
+	/**
+	 * 有效期
+	 */
+	@ApiModelProperty(value = "有效期")
+	private Date termValidity;
+	/**
+	 * 是否永久有效
+	 */
+	@ApiModelProperty(value = "是否永久有效")
+	private Boolean isForever;
+	/**
+	 * 是否设置密码
+	 */
+	@ApiModelProperty(value = "是否设置密码")
+	private Boolean isSetPasswd;
+}

+ 40 - 0
admin/src/main/java/org/springblade/modules/member/enums/AccountTypeEnum.java

@@ -0,0 +1,40 @@
+package org.springblade.modules.member.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 账号类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum AccountTypeEnum {
+
+	/**
+	 * 用户名
+	 */
+	USERNAME("username", "账号"),
+	/**
+	 * 手机
+	 */
+	PHONE("phone", "手机"),
+	/**
+	 * QQ
+	 */
+	QQ("qq", "QQ"),
+	/**
+	 * 微信网页
+	 */
+	WX_WEBSITE("wx_website", "微信网页"),
+	/**
+	 * 微信小程序
+	 */
+	WX_MINI_APP("wx_mini_app", "微信小程序"),
+
+	;
+
+	private final String code;
+	public final String desc;
+
+}
+

+ 37 - 0
admin/src/main/java/org/springblade/modules/member/enums/DateTypeEnum.java

@@ -0,0 +1,37 @@
+package org.springblade.modules.member.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 时间类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum DateTypeEnum {
+
+	/**
+	 * 今日
+	 */
+	TODAY("today", "今日"),
+	/**
+	 * 近7天
+	 */
+	REC7DAY("rec7day", "近7天"),
+	/**
+	 * 本月
+	 */
+	MONTH("month", "本月"),
+
+	/**
+	 * 本月还有的会员生日人数(剩余的) // 额外放置的类型
+	 */
+	MONTHSURPLUS("monthSurplus", "本月还有的会员生日人数(剩余的)"),
+
+	;
+
+	private final String code;
+	public final String desc;
+
+}
+

+ 32 - 0
admin/src/main/java/org/springblade/modules/member/enums/MemberAuditStatusEnum.java

@@ -0,0 +1,32 @@
+package org.springblade.modules.member.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 会员审核状态
+ */
+@Getter
+@AllArgsConstructor
+public enum MemberAuditStatusEnum {
+
+	/**
+	 * 待审核
+	 */
+	PENDING("pending", "待审核"),
+	/**
+	 * 审核通过
+	 */
+	PASS("pass", "审核通过"),
+	/**
+	 * 审核不通过
+	 */
+	FAILURE("failure", "审核不通过"),
+
+	;
+
+	private final String code;
+	public final String desc;
+
+}
+

+ 24 - 0
admin/src/main/java/org/springblade/modules/member/enums/SexEnum.java

@@ -0,0 +1,24 @@
+package org.springblade.modules.member.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 性别类型
+ */
+@Getter
+@AllArgsConstructor
+public enum SexEnum {
+
+	/**
+	 * 男士
+	 */
+	MAN(1, "男士"),
+	/**
+	 * 女士
+	 */
+	WOMAN(2, "女士");
+
+	private final Integer key;
+	private final String label;
+}

+ 13 - 0
admin/src/main/java/org/springblade/modules/member/mapper/MemberAccountMapper.java

@@ -0,0 +1,13 @@
+package org.springblade.modules.member.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+
+/**
+ *  Mapper 接口
+ *
+ * @author Chill
+ */
+public interface MemberAccountMapper extends BaseMapper<MemberAccountEntity> {
+
+}

+ 10 - 23
admin/src/main/java/org/springblade/modules/ycwh/mapper/AccountMapper.xml

@@ -1,36 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="org.springblade.modules.ycwh.mapper.AccountMapper">
+<mapper namespace="org.springblade.modules.member.mapper.MemberAccountMapper">
 
     <!-- 通用查询映射结果 -->
-    <resultMap id="accountResultMap" type="org.springblade.modules.ycwh.entity.AccountEntity">
+    <resultMap id="member_accountResultMap" type="org.springblade.modules.member.entity.MemberAccountEntity">
         <result column="id" property="id"/>
-        <result column="create_user" property="createUser"/>
-        <result column="create_dept" property="createDept"/>
-        <result column="create_time" property="createTime"/>
-        <result column="update_user" property="updateUser"/>
-        <result column="update_time" property="updateTime"/>
-        <result column="status" property="status"/>
-        <result column="is_deleted" property="isDeleted"/>
-        <result column="remark" property="remark"/>
-        <result column="sort" property="sort"/>
-        <result column="type" property="type"/>
         <result column="account" property="account"/>
         <result column="append_account" property="appendAccount"/>
+        <result column="account_type" property="accountType"/>
         <result column="password" property="password"/>
         <result column="salt" property="salt"/>
-        <result column="loginstyle" property="loginstyle"/>
+        <result column="is_enabled" property="isEnabled"/>
         <result column="member_id" property="memberId"/>
+        <result column="tenant_id" property="tenantId"/>
+        <result column="sort" property="sort"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
     </resultMap>
 
-
-    <select id="selectAccountPage" resultMap="accountResultMap">
-        select * from ycwh_account where is_deleted = 0
-    </select>
-
-
-    <select id="exportAccount" resultType="org.springblade.modules.ycwh.excel.AccountExcel">
-        SELECT * FROM ycwh_account ${ew.customSqlSegment}
-    </select>
-
 </mapper>

+ 26 - 0
admin/src/main/java/org/springblade/modules/member/mapper/MemberMapper.java

@@ -0,0 +1,26 @@
+package org.springblade.modules.member.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import io.lettuce.core.dynamic.annotation.Param;
+import org.springblade.modules.member.entity.MemberEntity;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ *  Mapper 接口
+ *
+ * @author Chill
+ */
+public interface MemberMapper extends BaseMapper<MemberEntity> {
+
+	/**
+	 * 根据日期获取会员生日列表
+	 * @param startDate
+	 * @param endDate
+	 * @return
+	 */
+	List<MemberEntity> getBirMemByDate(@Param(value = "startDate") Date startDate, @Param(value = "endDate") Date endDate);
+
+}

+ 68 - 0
admin/src/main/java/org/springblade/modules/member/mapper/MemberMapper.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.modules.member.mapper.MemberMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="member_infoResultMap" type="org.springblade.modules.member.entity.MemberEntity">
+        <result column="id" property="id"/>
+        <result column="from_product_code" property="fromProductCode"/>
+<!--        <result column="account" property="account"/>-->
+<!--        <result column="password" property="password"/>-->
+        <result column="pay_password" property="payPassword"/>
+        <result column="reg_ip" property="regIp"/>
+        <result column="register_time" property="registerTime"/>
+        <result column="audit_status" property="auditStatus"/>
+        <result column="area_code" property="areaCode"/>
+        <result column="address" property="address"/>
+        <result column="household_address" property="householdAddress"/>
+        <result column="phone" property="phone"/>
+        <result column="tel" property="tel"/>
+        <result column="level_id" property="levelId"/>
+        <result column="sex" property="sex"/>
+        <result column="birthday" property="birthday"/>
+        <result column="email" property="email"/>
+        <result column="nick_name" property="nickName"/>
+        <result column="real_name" property="realName"/>
+        <result column="member_code" property="memberCode"/>
+        <result column="fav_lang" property="favLang"/>
+        <result column="fav_currency" property="favCurrency"/>
+        <result column="login_count" property="loginCount"/>
+        <result column="consume_count" property="consumeCount"/>
+        <result column="order_count" property="orderCount"/>
+        <result column="avator" property="avator"/>
+        <result column="qrcode" property="qrcode"/>
+        <result column="tenant_id" property="tenantId"/>
+        <result column="point" property="point"/>
+        <result column="deposit" property="deposit"/>
+        <result column="advances" property="advances"/>
+        <result column="referral_code" property="referralCode"/>
+        <result column="need_point" property="needPoint"/>
+        <result column="sort" property="sort"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_dept" property="createDept"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="term_validity" property="termValidity"/>
+        <result column="is_forever" property="isForever"/>
+        <result column="is_set_passwd" property="isSetPasswd"/>
+    </resultMap>
+
+    <select id="getBirMemByDate" resultMap="member_infoResultMap">
+        SELECT *
+        FROM gr_member_info
+        WHERE DATE_FORMAT(birthday,"%m-%d") <![CDATA[ >= ]]> DATE_FORMAT(#{startDate},"%m-%d")
+        And DATE_FORMAT(birthday,"%m-%d") <![CDATA[ <= ]]> DATE_FORMAT(#{endDate},"%m-%d")
+        AND is_deleted = '0'
+        ORDER BY Format(birthday,"mmdd")
+    </select>
+
+    <select id="getTotalAdvances" resultType="java.math.BigDecimal">
+        select COALESCE(sum(advances), 0)
+        from gr_member_info
+        where is_deleted = '0'
+    </select>
+
+</mapper>

+ 10 - 0
admin/src/main/java/org/springblade/modules/member/mapper/member_account.sql

@@ -0,0 +1,10 @@
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672055065513990', 0, 'member', 'member_account', 'menu', '/member/member_account', NULL, 1, 1, 0, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672055065513991', '1424672055065513990', 'member_account_add', '新增', 'add', '/member/member_account/add', 'plus', 1, 2, 1, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672055065513992', '1424672055065513990', 'member_account_edit', '修改', 'edit', '/member/member_account/edit', 'form', 2, 2, 2, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672055065513993', '1424672055065513990', 'member_account_delete', '删除', 'delete', '/api/member/member_account/remove', 'delete', 3, 2, 3, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672055065513994', '1424672055065513990', 'member_account_view', '查看', 'view', '/member/member_account/view', 'file-text', 4, 2, 2, 1, NULL, 0);

+ 10 - 0
admin/src/main/java/org/springblade/modules/member/mapper/member_info.sql

@@ -0,0 +1,10 @@
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672061407301639', 0, 'member', 'member_info', 'menu', '/member/member_info', NULL, 1, 1, 0, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672061407301640', '1424672061407301639', 'member_info_add', '新增', 'add', '/member/member_info/add', 'plus', 1, 2, 1, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672061407301641', '1424672061407301639', 'member_info_edit', '修改', 'edit', '/member/member_info/edit', 'form', 2, 2, 2, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672061407301642', '1424672061407301639', 'member_info_delete', '删除', 'delete', '/api/member/member_info/remove', 'delete', 3, 2, 3, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES ('1424672061407301643', '1424672061407301639', 'member_info_view', '查看', 'view', '/member/member_info/view', 'file-text', 4, 2, 2, 1, NULL, 0);

+ 30 - 0
admin/src/main/java/org/springblade/modules/member/service/IMemberAccountService.java

@@ -0,0 +1,30 @@
+package org.springblade.modules.member.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+
+/**
+ *  服务类
+ *
+ * @author Chill
+ */
+public interface IMemberAccountService extends BaseService<MemberAccountEntity> {
+
+	/**
+	 * 微信小程序静默登录
+	 * @param clientCode 客户端编码,比如blocktime, dunbar
+	 * @param code 微信小程序静默登录生成的code
+	 * @return
+	 */
+	Kv doWxMiniappLogin(String clientCode, String code, String tenantId);
+
+	/**
+	 * 微信小程序注册
+	 * @param clientCode 客户端编码,比如blocktime, dunbar
+	 * @param code 微信小程序静默登录生成的code
+	 * @return
+	 */
+	Kv doWxMiniappRegister(String clientCode, String code);
+
+}

+ 72 - 0
admin/src/main/java/org/springblade/modules/member/service/IMemberInfoService.java

@@ -0,0 +1,72 @@
+package org.springblade.modules.member.service;
+
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.modules.member.dto.MemberDTO;
+import org.springblade.modules.member.entity.MemberEntity;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 服务类
+ *
+ * @author Chill
+ */
+public interface IMemberInfoService extends BaseService<MemberEntity> {
+
+
+	String getRandomName();
+
+	String getRandomAvatar();
+
+	MemberEntity addMember(String fromProductCode, String tenantId);
+
+
+	//TODO chenjs 以下方法是遗留方法,以后再改造
+
+	/**
+	 * 判断手机号码是否存在
+	 *
+	 * @param phone
+	 * @return
+	 */
+	Boolean getMemPhone(String phone);
+
+	/**
+	 * @param memberInfoDTO
+	 * @return
+	 */
+	MemberEntity setMemberInfoEntity(MemberDTO memberInfoDTO);
+
+	/**
+	 * 根据日期获取会员生日列表
+	 *
+	 * @param dateType
+	 * @return
+	 */
+	List<MemberEntity> getBirMemByDate(String dateType);
+
+	/**
+	 * 根据会员编号获取会员信息
+	 *
+	 * @param memberCode
+	 * @return
+	 */
+	MemberEntity getOneByMemberCode(String memberCode);
+
+	/**
+	 * 根据手机号码获取会员信息
+	 *
+	 * @param phone
+	 * @return
+	 */
+	MemberEntity getMemberInfoByPhone(String phone);
+
+	/**
+	 * 会员增长量
+	 * @param includeDel
+	 * @return
+	 */
+	BigDecimal averageDailyIncrease(Boolean includeDel);
+}

+ 199 - 0
admin/src/main/java/org/springblade/modules/member/service/impl/MemberAccountServiceImpl.java

@@ -0,0 +1,199 @@
+package org.springblade.modules.member.service.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.secure.provider.IClientDetails;
+import org.springblade.core.secure.provider.IClientDetailsService;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+import org.springblade.modules.member.entity.MemberEntity;
+import org.springblade.modules.member.enums.AccountTypeEnum;
+import org.springblade.modules.member.mapper.MemberAccountMapper;
+import org.springblade.modules.member.service.IMemberAccountService;
+import org.springblade.modules.member.service.IMemberInfoService;
+import org.springblade.modules.member.util.WXTokenUtil;
+import org.springblade.modules.member.vo.MemberVO;
+import org.springblade.modules.member.wx.config.WxMaConfiguration;
+import org.springblade.modules.member.wx.dto.LoginTokenDTO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+/**
+ * 服务实现类
+ *
+ * @author Chill
+ */
+@Service
+@AllArgsConstructor
+public class MemberAccountServiceImpl extends BaseServiceImpl<MemberAccountMapper, MemberAccountEntity> implements IMemberAccountService {
+
+	private IMemberInfoService memberInfoService;
+
+	private IClientDetailsService clientDetailsService;
+
+	@Override
+	public Kv doWxMiniappRegister(String clientCode, String code) {
+		return null;
+	}
+
+	@Override
+	@Transactional(
+		rollbackFor = {Exception.class}
+	)
+	public Kv doWxMiniappLogin(String clientCode, String code, String tenantId) {
+
+		WxMaJscode2SessionResult session = this.getWxSession(clientCode, code);
+
+		Kv ret = this.SignIn(AccountTypeEnum.WX_MINI_APP.getCode(), clientCode, session.getOpenid(), session.getUnionid(), tenantId);
+		if (Func.isNull(ret)) {
+			ret = this.signUp(AccountTypeEnum.WX_MINI_APP.getCode(), clientCode, session.getOpenid(), session.getUnionid(), tenantId);
+		}
+
+		return ret;
+	}
+
+	//登录
+	private Kv SignIn(String accountType, String clientCode, String account, String appendAccount, String tenantId) {
+		MemberAccountEntity accountEntity = this.getMemberAccount(accountType, account, appendAccount, tenantId);
+
+		//新用户,由注册流程返回信息
+		if (Func.isEmpty(accountEntity)) {
+			return null;
+		}
+
+		Long memberId = accountEntity.getMemberId();
+		String effective = "";
+
+		MemberEntity member = memberInfoService.getById(memberId);
+
+		LoginTokenDTO loginToken = this.getLoginToken(member, accountEntity.getAccount());
+
+
+		return this.getAuthInfo(loginToken, effective);
+	}
+
+	//注册
+	private Kv signUp(String accountType, String clientCode, String account, String appendAccount, String tenantId) {
+		//1. 写member表
+		MemberEntity member = memberInfoService.addMember(clientCode, tenantId);
+
+		//2. 写member_account表
+		MemberAccountEntity accountEntity = this.addAccount(member.getId(), accountType, account, appendAccount, tenantId);
+
+
+		Long memberId = accountEntity.getMemberId();
+		String effective = "";
+
+		LoginTokenDTO loginToken = this.getLoginToken(member, accountEntity.getAccount());
+
+		return this.getAuthInfo(loginToken, effective);
+	}
+
+	//TODO chenjs 改造返回前端的内容,看看是否有必要返回account
+	private LoginTokenDTO getLoginToken(MemberEntity member, String account) {
+		LoginTokenDTO loginToken = new LoginTokenDTO();
+
+		MemberVO memberVO = Func.copyProperties(member, MemberVO.class);
+
+		loginToken.setId(member.getId());
+		loginToken.setAccount(account);
+		loginToken.setAvator(member.getAvator());
+		loginToken.setRealname(member.getRealName());
+		loginToken.setNickName(member.getNickName());
+		loginToken.setStatus(member.getStatus());
+		loginToken.setMemberInfo(memberVO);
+		loginToken.setTenantId(memberVO.getTenantId());
+
+		return loginToken;
+	}
+
+	//获取微信会话
+	private WxMaJscode2SessionResult getWxSession(String clientCode, String code) {
+		try {
+			IClientDetails clientDetail = clientDetailsService.loadClientByClientId(clientCode);
+			if(Func.isEmpty(clientDetail)) {
+				throw new ServiceException("应用id不存在");
+			}
+			final WxMaService wxService = WxMaConfiguration.getMaService(clientDetail.getClientId());
+			return wxService.getUserService().getSessionInfo(code);
+		} catch (WxErrorException e) {
+			throw new ServiceException("微信登录失败" + e.getMessage());
+		}
+	}
+
+
+	//找到现有的会员账号
+	private MemberAccountEntity getMemberAccount(String accountType, String account, String appendAccount, String tenantId) {
+		//先用unionId找账号
+		MemberAccountEntity accountEntity = this.getOne(Wrappers.<MemberAccountEntity>lambdaQuery()
+			.eq(MemberAccountEntity::getTenantId, tenantId)
+			.eq(MemberAccountEntity::getAccountType, accountType)
+			.eq(MemberAccountEntity::getAppendAccount, appendAccount)
+			.last("limit 1"));
+
+		if (Func.isNotEmpty(accountEntity)) {
+			return accountEntity;
+		}
+
+		//用unionId找不到账号之后再用openId找,因为第一版的用户只有openId
+		//TODO chenjs 目前还有22个2018年5月份进入小程序的用户没有重新登录新版本,下次检查一下,如果全部已经重新登录了就改造这个方法
+		accountEntity = this.getOne(Wrappers.<MemberAccountEntity>lambdaQuery()
+			.eq(MemberAccountEntity::getAccountType, accountType)
+			.eq(MemberAccountEntity::getAccount, account)
+			.last("limit 1"));
+
+		//如果找到了账号就将unionId写进去,下一次不再通过openId找
+		if (Func.isNotEmpty(accountEntity)) {
+			accountEntity.setAppendAccount(appendAccount);
+			this.updateById(accountEntity);
+		}
+
+		return accountEntity;
+	}
+
+
+	private MemberAccountEntity addAccount(Long memberId, String accountType, String account, String appendAccount, String tenantId) {
+
+		MemberAccountEntity accountEntity = new MemberAccountEntity();
+		accountEntity.setAccount(account);
+		accountEntity.setAppendAccount(appendAccount);
+		accountEntity.setAccountType(accountType);
+		accountEntity.setIsEnabled(Boolean.TRUE);
+		accountEntity.setMemberId(memberId);
+		accountEntity.setTenantId(tenantId);
+		this.save(accountEntity);
+
+		return accountEntity;
+	}
+
+	private Date getEffectiveDate(Date oldDate, Integer effectiveDays) {
+		Date newDate = DateUtil.now();
+		if (Func.isEmpty(oldDate)) {
+			oldDate = DateUtil.now();
+		}
+		if (newDate.after(oldDate)) {
+			return DateUtil.plusDays(newDate, effectiveDays);
+		} else {
+			return DateUtil.plusDays(oldDate, effectiveDays);
+		}
+	}
+
+	private Kv getAuthInfo(LoginTokenDTO loginToken, String effective) {
+		Kv kv = WXTokenUtil.createAuthInfo(loginToken);
+
+		kv.set("tokenExpire", DateUtil.plusSeconds(DateUtil.now(), kv.getLong(TokenConstant.EXPIRES_IN)));
+		kv.set("equityEffective", effective);
+
+		return kv;
+	}
+}

+ 191 - 0
admin/src/main/java/org/springblade/modules/member/service/impl/MemberInfoServiceImpl.java

@@ -0,0 +1,191 @@
+package org.springblade.modules.member.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springblade.common.cache.SysCache;
+import org.springblade.core.launch.utils.INetUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tenant.BladeTenantProperties;
+import org.springblade.core.tool.constant.BladeConstant;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.*;
+import org.springblade.modules.member.dto.MemberDTO;
+import org.springblade.modules.member.entity.MemberEntity;
+import org.springblade.modules.member.enums.DateTypeEnum;
+import org.springblade.modules.member.enums.MemberAuditStatusEnum;
+import org.springblade.modules.member.mapper.MemberMapper;
+import org.springblade.modules.member.service.IMemberInfoService;
+import org.springblade.modules.system.entity.Tenant;
+import org.springframework.stereotype.Service;
+import org.springblade.modules.member.util.DateUtil;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 服务实现类
+ *
+ * @author Chill
+ */
+@Service
+
+@AllArgsConstructor
+public class MemberInfoServiceImpl extends BaseServiceImpl<MemberMapper, MemberEntity> implements IMemberInfoService {
+
+	private final BladeTenantProperties tenantProperties;
+
+	@Override
+	public String getRandomName() {
+		return "随机名字";
+	}
+
+	@Override
+	public String getRandomAvatar() {
+		return "http://cdn.zhenwopai.com/avatar/1564114490560";
+	}
+
+	@Override
+	public MemberEntity addMember(String fromProductCode, String tenantId) {
+		MemberEntity memberEntity = new MemberEntity();
+		memberEntity.setFromProductCode(fromProductCode);
+		memberEntity.setNickName(this.getRandomName());
+		memberEntity.setAvator(this.getRandomAvatar());
+		memberEntity.setRegIp(INetUtil.getHostIp());
+		memberEntity.setRegisterTime(DateUtil.now());
+		memberEntity.setAuditStatus(MemberAuditStatusEnum.PASS.getCode());//默认审核通过
+		memberEntity.setTenantId(this.addTenantId(tenantId));
+		this.save(memberEntity);
+
+		return memberEntity;
+	}
+
+	private String addTenantId(String tenantId) {
+		if (StringUtil.isBlank(tenantId)) {
+			tenantId = BladeConstant.ADMIN_TENANT_ID;
+		}
+		Tenant tenant = SysCache.getTenant(tenantId);
+		if (Func.isNotEmpty(tenant)) {
+			Integer accountNumber = tenant.getAccountNumber();
+			if (tenantProperties.getLicense()) {
+				String licenseKey = tenant.getLicenseKey();
+				String decrypt = DesUtil.decryptFormHex(licenseKey, "0000000000000000");
+				accountNumber = JsonUtil.parse(decrypt, Tenant.class).getAccountNumber();
+			}
+			Long tenantCount = baseMapper.selectCount(Wrappers.<MemberEntity>query().lambda().eq(MemberEntity::getTenantId, tenantId));
+			if (accountNumber != null && accountNumber > 0 && accountNumber <= tenantCount) {
+				throw new ServiceException("当前租户已到最大账号额度!");
+			}
+		}
+		return tenantId;
+	}
+
+
+// TODO chenjs 以下方法是遗留方法,先放着,以后再重构
+
+
+	@Override
+	public Boolean getMemPhone(String phone) {
+		MemberEntity entity = new MemberEntity();
+		entity.setPhone(phone);
+		QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>(entity);
+		wrapper.last("limit 1");
+		if (Func.isNotEmpty(this.getOne(wrapper))) {
+			return true;
+		} else {
+			return false;
+		}
+
+	}
+
+	@Override
+	public MemberEntity setMemberInfoEntity(MemberDTO memberInfoDTO) {
+		MemberEntity memberInfoEntity = BeanUtil.copy(memberInfoDTO, MemberEntity.class);
+		//密码
+		if (Func.isNotEmpty(memberInfoDTO.getPayPassword())) {
+			memberInfoEntity.setPayPassword(DigestUtil.hex(memberInfoDTO.getPayPassword()));//加密
+		} else {//密码为空的时候不改变原来的值
+			QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>();
+			wrapper.eq("id", memberInfoEntity.getId());
+			MemberEntity entity = this.getOne(wrapper);
+			if (Func.isNotEmpty(entity)) {
+				memberInfoEntity.setPayPassword(entity.getPayPassword());
+			}
+		}
+
+		return memberInfoEntity;
+	}
+
+	@Override
+	public List<MemberEntity> getBirMemByDate(String dateType) {
+		if (dateType.equals(DateTypeEnum.TODAY.getCode())) {//今日
+			List<MemberEntity> birMemByDate = baseMapper.getBirMemByDate(DateUtil.getDayBegin(), DateUtil.getDayEnd());
+			return birMemByDate;
+		} else if (dateType.equals(DateTypeEnum.REC7DAY.getCode())) {//近7天
+			List<MemberEntity> birMemByDate = baseMapper.getBirMemByDate(DateUtil.getFrontDay(new Date(), 7), DateUtil.getDayEnd());
+			return birMemByDate;
+		} else if (dateType.equals(DateTypeEnum.MONTH.getCode())) {//本月
+			List<MemberEntity> birMemByDate = baseMapper.getBirMemByDate(DateUtil.getBeginDayOfMonth(), DateUtil.getEndDayOfMonth());
+			return birMemByDate;
+		} else if (dateType.equals(DateTypeEnum.MONTHSURPLUS.getCode())) {//本月还有的会员生日人数(剩余的)
+			List<MemberEntity> birMemByDate = baseMapper.getBirMemByDate(DateUtil.getDayEnd(), DateUtil.getEndDayOfMonth());
+			return birMemByDate;
+		} else {
+			return null;
+		}
+	}
+
+	//统计第一位会员注册日期到今天之间的天数
+	public static Integer getDayNum(Date startTime) {
+		long targetTime = startTime.getTime();
+		long todayTime = new Date().getTime();
+		long time = Math.abs(todayTime - targetTime);
+		float day = (float) time / (float) (1000 * 60 * 60 * 24);
+		Integer ceil = Integer.valueOf((int) Math.ceil(day));
+		return ceil;
+	}
+
+	@Override
+	public MemberEntity getOneByMemberCode(String memberCode) {
+		MemberEntity entity = new MemberEntity();
+		entity.setMemberCode(memberCode);
+		QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>(entity);
+		wrapper.last("limit 1");
+		return this.getOne(wrapper);
+	}
+
+	@Override
+	public MemberEntity getMemberInfoByPhone(String phone) {
+		if (Func.isEmpty(phone)) {
+			return null;
+		}
+		MemberEntity entity = new MemberEntity();
+		entity.setPhone(phone);
+		QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>(entity);
+		wrapper.last("limit 1");
+		return this.getOne(wrapper);
+	}
+	@Override
+	public BigDecimal averageDailyIncrease(Boolean includeDel) {
+		QueryWrapper<MemberEntity> wrapper = new QueryWrapper<>();
+		wrapper.orderByAsc("create_time");
+
+		//是否过滤掉过期的、退卡的、删除了的会员
+		if (Func.isNotEmpty(includeDel)) {
+			if (includeDel) {//是
+				wrapper.eq("deleted", 0).or().eq("deleted", 1);
+			}
+		}
+
+		List<MemberEntity> list = this.list(wrapper);
+		Date createTime = list.get(0).getCreateTime();
+		Integer dayNum = getDayNum(createTime);
+
+		BigDecimal averageDaily = new BigDecimal(list.size()).divide(new BigDecimal(dayNum), 2, RoundingMode.HALF_UP);
+
+		return averageDaily;
+	}
+}

+ 116 - 0
admin/src/main/java/org/springblade/modules/member/util/DateUtil.java

@@ -0,0 +1,116 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.member.util;
+
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ * 通用工具类
+ *
+ * @author Chill
+ */
+public class DateUtil {
+
+	// 获取当天的开始时间
+	public static Date getDayBegin() {
+		Calendar cal = new GregorianCalendar();
+		cal.set(Calendar.HOUR_OF_DAY, 0);
+		cal.set(Calendar.MINUTE, 0);
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
+	}
+
+	// 获取当天的结束时间
+	public static Date getDayEnd() {
+		Calendar cal = new GregorianCalendar();
+		cal.set(Calendar.HOUR_OF_DAY, 23);
+		cal.set(Calendar.MINUTE, 59);
+		cal.set(Calendar.SECOND, 59);
+		return cal.getTime();
+	}
+
+	// 返回某个日期前几天的日期
+	public static Date getFrontDay(Date date, int i) {
+		Calendar cal = new GregorianCalendar();
+		cal.setTime(date);
+		cal.set(Calendar.DATE, cal.get(Calendar.DATE) - i);
+		return cal.getTime();
+	}
+
+	// 获取今年是哪一年
+	public static Integer getNowYear() {
+		Date date = new Date();
+		GregorianCalendar gc = (GregorianCalendar) Calendar.getInstance();
+		gc.setTime(date);
+		return Integer.valueOf(gc.get(1));
+	}
+
+	// 获取本月是哪一月
+	public static int getNowMonth() {
+		Date date = new Date();
+		GregorianCalendar gc = (GregorianCalendar) Calendar.getInstance();
+		gc.setTime(date);
+		return gc.get(2) + 1;
+	}
+
+	// 获取某个日期的开始时间
+	public static Timestamp getDayStartTime(Date d) {
+		Calendar calendar = Calendar.getInstance();
+		if (null != d)
+			calendar.setTime(d);
+		calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+			calendar.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+		calendar.set(Calendar.MILLISECOND, 0);
+		return new Timestamp(calendar.getTimeInMillis());
+	}
+
+	// 获取某个日期的结束时间
+	public static Timestamp getDayEndTime(Date d) {
+		Calendar calendar = Calendar.getInstance();
+		if (null != d)
+			calendar.setTime(d);
+		calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+			calendar.get(Calendar.DAY_OF_MONTH), 23, 59, 59);
+		calendar.set(Calendar.MILLISECOND, 999);
+		return new Timestamp(calendar.getTimeInMillis());
+	}
+
+
+	// 获取本月的开始时间
+	public static Date getBeginDayOfMonth() {
+		Calendar calendar = Calendar.getInstance();
+		calendar.set(getNowYear(), getNowMonth() - 1, 1);
+		return getDayStartTime(calendar.getTime());
+	}
+
+	// 获取本月的结束时间
+	public static Date getEndDayOfMonth() {
+		Calendar calendar = Calendar.getInstance();
+		calendar.set(getNowYear(), getNowMonth() - 1, 1);
+		int day = calendar.getActualMaximum(5);
+		calendar.set(getNowYear(), getNowMonth() - 1, day);
+		return getDayEndTime(calendar.getTime());
+	}
+
+	public static Date now() {
+		return new Date();
+	}
+}

+ 148 - 0
admin/src/main/java/org/springblade/modules/member/util/WXTokenUtil.java

@@ -0,0 +1,148 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.member.util;
+
+import org.springblade.core.launch.constant.TokenConstant;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.secure.TokenInfo;
+import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.tenant.BladeTenantProperties;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.modules.member.wx.dto.LoginTokenDTO;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 认证工具类
+ *
+ * @author Chill
+ */
+public class WXTokenUtil {
+
+	public final static String CAPTCHA_HEADER_KEY = "Captcha-Key";
+	public final static String CAPTCHA_HEADER_CODE = "Captcha-Code";
+	public final static String CAPTCHA_NOT_CORRECT = "验证码不正确";
+	public final static String TENANT_HEADER_KEY = "Tenant-Id";
+	public final static String DEFAULT_TENANT_ID = "000000";
+	public final static String USER_TYPE_HEADER_KEY = "User-Type";
+	public final static String DEFAULT_USER_TYPE = "web";
+	public final static String PC_USER_TYPE = "pc"; // PC端(区分于超级管理后台)
+	public final static String USER_NOT_FOUND = "用户名或密码错误";
+	public final static String USER_HAS_NO_ROLE = "未获得用户的角色信息";
+	public final static String USER_HAS_NO_TENANT = "未获得用户的租户信息";
+	public final static String USER_HAS_NO_TENANT_PERMISSION = "租户授权已过期,请联系管理员";
+	public final static String HEADER_KEY = "Authorization";
+	public final static String HEADER_PREFIX = "Basic ";
+	public final static String DEFAULT_AVATAR = "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png";
+
+	private static BladeTenantProperties tenantProperties;
+	private static BladeRedis bladeRedis;
+
+	/**
+	 * 获取租户配置
+	 *
+	 * @return tenantProperties
+	 */
+	private static BladeTenantProperties getTenantProperties() {
+		if (tenantProperties == null) {
+			tenantProperties = SpringUtil.getBean(BladeTenantProperties.class);
+		}
+		return tenantProperties;
+	}
+
+	/**
+	 * 获取租户配置
+	 *
+	 * @return tenantProperties
+	 */
+	private static BladeRedis getBladeRedis() {
+		if (bladeRedis == null) {
+			bladeRedis = SpringUtil.getBean(BladeRedis.class);
+		}
+		return bladeRedis;
+	}
+
+	/**
+	 * 创建认证token
+	 *
+	 * @param loginToken 用户信息
+	 * @return token
+	 */
+	public static Kv createAuthInfo(LoginTokenDTO loginToken) {
+		Kv authInfo = Kv.create();
+		//设置jwt参数
+		Map<String, Object> param = new HashMap<>(16);
+		param.put(TokenConstant.TOKEN_TYPE, TokenConstant.ACCESS_TOKEN);
+		param.put(TokenConstant.TENANT_ID, loginToken.getTenantId());
+		param.put(TokenConstant.USER_ID, Func.toStr(loginToken.getId()));
+		param.put(TokenConstant.DEPT_ID, null);
+		param.put(TokenConstant.POST_ID, null);
+		param.put(TokenConstant.ROLE_ID, null);
+		param.put(TokenConstant.OAUTH_ID, null);
+		param.put(TokenConstant.ACCOUNT, loginToken.getAccount());
+		param.put(TokenConstant.USER_NAME, loginToken.getAccount());
+		param.put(TokenConstant.NICK_NAME, loginToken.getNickName());
+//		param.put("user_connect_id", Func.toStr(loginToken.getId())); // 用户关联id
+//		param.put("identify_type", Func.toStr(loginToken.getIdentifyType())); // 用户身份
+
+		//拼装accessToken
+		try {
+			TokenInfo accessToken = SecureUtil.createJWT(param, "audience", "issuer", TokenConstant.ACCESS_TOKEN);
+
+			//返回accessToken
+			return authInfo.set(TokenConstant.TENANT_ID, loginToken.getTenantId())
+				.set(TokenConstant.USER_ID, Func.toStr(loginToken.getId()))
+				.set(TokenConstant.DEPT_ID, null)
+				.set(TokenConstant.POST_ID, null)
+				.set(TokenConstant.ROLE_ID, null)
+				.set(TokenConstant.ACCOUNT, loginToken.getAccount())
+				.set(TokenConstant.USER_NAME, loginToken.getAccount())
+				.set(TokenConstant.NICK_NAME, loginToken.getNickName())
+				.set(TokenConstant.AVATAR, Func.toStr(loginToken.getAvator(), TokenConstant.DEFAULT_AVATAR))
+				.set(TokenConstant.ACCESS_TOKEN, accessToken.getToken())
+				.set(TokenConstant.REFRESH_TOKEN, createRefreshToken(loginToken).getToken())
+				.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
+				.set(TokenConstant.EXPIRES_IN, accessToken.getExpire())
+
+//				.set(TokenConstant.EXPIRES_IN, 60*60*24*7) //7天过期
+				.set(TokenConstant.LICENSE, "powered by Live4Real") //TokenConstant.LICENSE_NAME
+//				.set("user_connect_id", Func.toStr(loginToken.getId())) // 用户关联id
+//				.set("identify_type", Func.toStr(loginToken.getIdentifyType())) // 用户身份
+				;
+		} catch (Exception ex) {
+			return authInfo.set("error_code", HttpServletResponse.SC_UNAUTHORIZED).set("error_description", ex.getMessage());
+		}
+	}
+
+	/**
+	 * 创建refreshToken
+	 *
+	 * @param loginToken 用户信息
+	 * @return refreshToken
+	 */
+	private static TokenInfo createRefreshToken(LoginTokenDTO loginToken) {
+		Map<String, Object> param = new HashMap<>(16);
+		param.put(TokenConstant.TOKEN_TYPE, TokenConstant.REFRESH_TOKEN);
+		param.put(TokenConstant.USER_ID, Func.toStr(loginToken.getId()));
+		return SecureUtil.createJWT(param, "audience", "issuser", TokenConstant.REFRESH_TOKEN);
+	}
+
+}

+ 18 - 0
admin/src/main/java/org/springblade/modules/member/vo/MemberAccountVO.java

@@ -0,0 +1,18 @@
+package org.springblade.modules.member.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+
+/**
+ *  模型VO
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemberAccountVO extends MemberAccountEntity {
+
+	private static final long serialVersionUID = 1L;
+
+}

+ 31 - 0
admin/src/main/java/org/springblade/modules/member/vo/MemberVO.java

@@ -0,0 +1,31 @@
+package org.springblade.modules.member.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.modules.member.entity.MemberEntity;
+
+/**
+ * 模型VO
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MemberVO extends MemberEntity {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 性别
+	 */
+	@ApiModelProperty("性别")
+	private String sexName;
+
+	/**
+	 * 门店名称
+	 */
+	@ApiModelProperty("门店名称")
+	private String shopName;
+
+}

+ 31 - 0
admin/src/main/java/org/springblade/modules/member/wrapper/MemberAccountWrapper.java

@@ -0,0 +1,31 @@
+package org.springblade.modules.member.wrapper;
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.modules.member.entity.MemberAccountEntity;
+import org.springblade.modules.member.vo.MemberAccountVO;
+
+/**
+ *  包装类,返回视图层所需的字段
+ *
+ * @author Chill
+ */
+public class MemberAccountWrapper extends BaseEntityWrapper<MemberAccountEntity, MemberAccountVO> {
+
+	public static MemberAccountWrapper build() {
+		return new MemberAccountWrapper();
+ 	}
+
+	@Override
+	public MemberAccountVO entityVO(MemberAccountEntity member_account) {
+		MemberAccountVO member_accountVO = BeanUtil.copy(member_account, MemberAccountVO.class);
+
+		//User createUser = UserCache.getUser(member_account.getCreateUser());
+		//User updateUser = UserCache.getUser(member_account.getUpdateUser());
+		//member_accountVO.setCreateUserName(createUser.getName());
+		//member_accountVO.setUpdateUserName(updateUser.getName());
+
+		return member_accountVO;
+	}
+
+}

+ 48 - 0
admin/src/main/java/org/springblade/modules/member/wrapper/MemberInfoWrapper.java

@@ -0,0 +1,48 @@
+package org.springblade.modules.member.wrapper;
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.modules.member.entity.MemberEntity;
+import org.springblade.modules.member.enums.SexEnum;
+import org.springblade.modules.member.service.IMemberInfoService;
+import org.springblade.modules.member.vo.MemberVO;
+
+/**
+ *  包装类,返回视图层所需的字段
+ *
+ * @author Chill
+ */
+public class MemberInfoWrapper extends BaseEntityWrapper<MemberEntity, MemberVO> {
+
+	public static MemberInfoWrapper build() {
+		return new MemberInfoWrapper();
+ 	}
+
+	private static IMemberInfoService memberInfoService;
+
+	static {
+		memberInfoService = SpringUtil.getBean(IMemberInfoService.class);
+	}
+
+	@Override
+	public MemberVO entityVO(MemberEntity memberInfo) {
+		if (Func.isEmpty(memberInfo)) {
+			return null;
+		}
+		MemberVO member_infoVO = BeanUtil.copy(memberInfo, MemberVO.class);
+		//性别
+		if (Func.isNotEmpty(member_infoVO.getSex())) {
+			if (member_infoVO.getSex().equals("1")) {
+				member_infoVO.setSexName(SexEnum.MAN.getLabel());
+
+			} else {
+				member_infoVO.setSexName(SexEnum.WOMAN.getLabel());
+			}
+		}
+
+		return member_infoVO;
+	}
+
+}

+ 147 - 0
admin/src/main/java/org/springblade/modules/member/wx/config/WxMaConfiguration.java

@@ -0,0 +1,147 @@
+package org.springblade.modules.member.wx.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
+import cn.binarywang.wx.miniapp.bean.WxMaTemplateData;
+import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
+import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ */
+@Configuration
+@EnableConfigurationProperties(WxMaProperties.class)
+public class WxMaConfiguration {
+	private WxMaProperties properties;
+
+	private static Map<String, WxMaMessageRouter> routers = Maps.newHashMap();
+	private static Map<String, WxMaService> maServices = Maps.newHashMap();
+
+	@Autowired
+	public WxMaConfiguration(WxMaProperties properties) {
+		this.properties = properties;
+	}
+
+	public static WxMaService getMaService(String appid) {
+		WxMaService wxService = maServices.get(appid);
+		if (wxService == null) {
+			throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
+		}
+
+		return wxService;
+	}
+
+	public static WxMaMessageRouter getRouter(String appid) {
+		return routers.get(appid);
+	}
+
+	@PostConstruct
+	public void init() {
+		List<WxMaProperties.Config> configs = this.properties.getConfigs();
+		if (configs == null) {
+			throw new RuntimeException("看下nacos是否有相关配置,注意别配错了!");
+		}
+
+		maServices = configs.stream()
+			.map(a -> {
+				WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+				config.setAppid(a.getAppid());
+				config.setSecret(a.getSecret());
+				config.setToken(a.getToken());
+				config.setAesKey(a.getAesKey());
+				config.setMsgDataFormat(a.getMsgDataFormat());
+
+				WxMaService service = new WxMaServiceImpl();
+				service.setWxMaConfig(config);
+				routers.put(a.getAppid(), this.newRouter(service));
+				return service;
+			}).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a));
+	}
+
+	private WxMaMessageRouter newRouter(WxMaService service) {
+		final WxMaMessageRouter router = new WxMaMessageRouter(service);
+		router
+			.rule().handler(logHandler).next()
+			.rule().async(false).content("模板").handler(templateMsgHandler).end()
+			.rule().async(false).content("文本").handler(textHandler).end()
+			.rule().async(false).content("图片").handler(picHandler).end()
+			.rule().async(false).content("二维码").handler(qrcodeHandler).end();
+		return router;
+	}
+
+	private final WxMaMessageHandler templateMsgHandler = (wxMessage, context, service, sessionManager) -> {
+		service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.builder()
+			.templateId("此处更换为自己的模板id")
+			.formId("自己替换可用的formid")
+			.data(Lists.newArrayList(
+				new WxMaTemplateData("keyword1", "339208499", "#173177")))
+			.toUser(wxMessage.getFromUser())
+			.build());
+		return null;
+	};
+
+	private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> {
+		System.out.println("收到消息:" + wxMessage.toString());
+		service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
+			.toUser(wxMessage.getFromUser()).build());
+		return null;
+	};
+
+	private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> {
+		service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
+			.toUser(wxMessage.getFromUser()).build());
+		return null;
+	};
+
+	private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> {
+		try {
+			WxMediaUploadResult uploadResult = service.getMediaService()
+				.uploadMedia("image", "png",
+					ClassLoader.getSystemResourceAsStream("tmp.png"));
+			service.getMsgService().sendKefuMsg(
+				WxMaKefuMessage
+					.newImageBuilder()
+					.mediaId(uploadResult.getMediaId())
+					.toUser(wxMessage.getFromUser())
+					.build());
+		} catch (WxErrorException e) {
+			e.printStackTrace();
+		}
+
+		return null;
+	};
+
+	private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> {
+		try {
+			final File file = service.getQrcodeService().createQrcode("123", 430);
+			WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
+			service.getMsgService().sendKefuMsg(
+				WxMaKefuMessage
+					.newImageBuilder()
+					.mediaId(uploadResult.getMediaId())
+					.toUser(wxMessage.getFromUser())
+					.build());
+		} catch (WxErrorException e) {
+			e.printStackTrace();
+		}
+
+		return null;
+	};
+
+}

+ 45 - 0
admin/src/main/java/org/springblade/modules/member/wx/config/WxMaProperties.java

@@ -0,0 +1,45 @@
+package org.springblade.modules.member.wx.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ */
+@Data
+@ConfigurationProperties(prefix = "wx.miniapp")
+public class WxMaProperties {
+
+    private List<Config> configs;
+
+    @Data
+    public static class Config {
+        /**
+         * 设置微信小程序的appid
+         */
+        private String appid;
+
+        /**
+         * 设置微信小程序的Secret
+         */
+        private String secret;
+
+        /**
+         * 设置微信小程序消息服务器配置的token
+         */
+        private String token;
+
+        /**
+         * 设置微信小程序消息服务器配置的EncodingAESKey
+         */
+        private String aesKey;
+
+        /**
+         * 消息格式,XML或者JSON
+         */
+        private String msgDataFormat;
+    }
+
+}

+ 62 - 0
admin/src/main/java/org/springblade/modules/member/wx/dto/LoginTokenDTO.java

@@ -0,0 +1,62 @@
+package org.springblade.modules.member.wx.dto;
+
+import lombok.Data;
+import org.springblade.modules.member.vo.MemberVO;
+
+import java.io.Serializable;
+
+/**
+ * 用户登录信息
+ */
+@Data
+public class LoginTokenDTO implements Serializable {
+
+	/**
+	 *用户id
+	 */
+	private Long id;
+	/**
+	 *用户账号id
+	 */
+	private Long accountId;
+	/**
+	 *账号(一般用微信openId或unionId来填充)
+	 */
+	private String account;
+
+	/**
+	 * 账号(会员设置的账号名)
+	 */
+	private String name;
+
+	/**
+	 * 真实姓名(身份证名称)
+	 */
+	private String realname;
+
+	/**
+	 *昵称,一般用微信昵称来填充
+	 */
+	private String nickName;
+
+	/**
+	 *头像(一般用微信头像路径来填充)
+	 */
+	private String avator ;
+
+	/**
+	 *状态
+	 */
+	private Integer status;
+
+	/**
+	 *会员信息(详细),包含了建档门店ID
+	 */
+	private MemberVO memberInfo;
+
+	/**
+	 * 租户id(单体版不需要赋值)
+	 */
+	private String tenantId;
+
+}